找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3595

积分

0

好友

483

主题
发表于 3 小时前 | 查看: 3| 回复: 0

在开发中,并发问题是每个 Java 项目迟早要面对的拦路虎。轻则接口超时、用户体验差;重则数据错乱、资损严重。

这篇文章整理了日常开发中8个高频并发踩坑点,每个坑都配代码和解决方案,看完直接落地。

1. synchronized 加错位置,白锁了

// ❌ 错误:锁的是方法,但锁不住类级别的并发
@Service
public class OrderService {

    private int stock = 100;

    public synchronized void createOrder() {
        // 以为锁住了,实际这只锁了方法对象
        if (stock > 0) {
            stock--;
        }
    }
}

// ✅ 正确:锁住的是 stock 这份资源本身
@Service
public class OrderService {

    private int stock = 100;
    private final Object lock = new Object();

    public void createOrder() {
        synchronized (lock) {
            if (stock > 0) {
                stock--;
            }
        }
    }
}

原则:synchronized 锁的是对象,不是代码块。要锁住的是共享资源,不是方法签名。

2. 用 Integer/Bool 装箱类型当锁对象,等于没锁

// ❌ 危险:Integer 有缓存,-128~127 是同一个对象
@Service
public class ProductService {

    private Map<String, Integer> stockMap = new ConcurrentHashMap<>();

    public void reduceStock(String productId) {
        Integer stock = stockMap.get(productId);
        synchronized (stock) {  // 不同 productId 可能拿到同一个 Integer 对象!
            stock--;
        }
    }
}

// ✅ 正确:锁对象用 final Object 或直接用 ConcurrentHashMap
@Service
public class ProductService {

    private Map<String, Integer> stockMap = new ConcurrentHashMap<>();
    private final Object lock = new Object();

    public void reduceStock(String productId) {
        // 推荐:使用 ConcurrentHashMap 的原子操作
        stockMap.computeIfAbsent(productId, k -> 100);
        stockMap.compute(productId, (k, v) -> v - 1);
    }
}

切记:锁对象必须是引用类型且唯一,不能用基本类型的包装类型。

3. ConcurrentHashMap 不是万能的,组合操作照样出事

// ❌ 错误:看似安全,实际不是原子操作
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1);
map.put("b", 2);

// 线程A
Integer a = map.get("a");
Integer b = map.get("b");

// 线程B同时执行
map.remove("a");
map.remove("b");
// 此时 a 和 b 的值可能已经不存在了

// ✅ 正确:使用原子操作
map.compute("a", (k, v) -> v == null ? 0 : v - 1);
map.merge("b", 1, Integer::sum);

// ✅ 或使用分段锁思维
private final ConcurrentHashMap<String, AtomicInteger> stocks = new ConcurrentHashMap<>();

public void reduceStock(String key) {
    stocks.computeIfAbsent(key, k -> new AtomicInteger(0)).decrementAndGet();
}

4. @Async 不生效,还不知道原因

// ❌ 错误:同类内部调用,@Async 失效(代理失效问题)
@Service
public class OrderService {

    public void createOrder() {
        // this.sendNotice() 绕过了 Spring 代理
        this.sendNotice();
    }

    @Async
    public void sendNotice() {
        // 这个方法根本不会异步执行
    }
}

// ✅ 正确:注入自身 Bean
@Service
public class OrderService {

    @Autowired
    private OrderService self;  // 注入代理对象

    public void createOrder() {
        self.sendNotice();  // 走代理,异步生效
    }

    @Async
    public void sendNotice() {
        // 现在会异步执行
    }
}

原则:同类内部调用走的是 this,不走 Spring 代理,AOP 注解全部失效。

5. 线程池用了不配置,等于埋雷

// ❌ 危险:默认线程池,问题一堆
@Configuration
public class AsyncConfig {

    @Bean
    public Executor asyncExecutor() {
        // 默认AbortPolicy,队列满了直接抛异常
        return Executors.newFixedThreadPool(10);
    }
}

// ✅ 正确:合理配置线程池
@Configuration
public class AsyncConfig {

    @Bean("customAsyncExecutor")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("async-");

        // 拒绝策略:交给调用线程执行
        executor.setRejectedExecutionHandler(
            new ThreadPoolExecutor.CallerRunsPolicy()
        );

        executor.initialize();
        return executor;
    }
}

核心参数选择:

场景 核心线程数 队列类型
CPU 密集 CPU核数+1 LinkedBlockingQueue
IO 密集 CPU核数×2 SynchronousQueue
混合型 CPU核数×(1+等待时间/运行时间) 有界队列

6. @Transactional 遇到多线程,事务是假的

// ❌ 危险:主线程事务,子线程独立
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    public void createOrder() {
        orderMapper.insert(order);

        // 开启子线程
        new Thread(() -> {
            // 这里独立的事务,子线程异常不会触发回滚
            noticeService.sendNotice(order.getId());
        }).start();
    }
}

// ✅ 正确:事务要在一个链路里
@Service
public class OrderService {

    @Transactional
    public void createOrder() {
        orderMapper.insert(order);
        noticeService.sendNotice(order.getId());  // 同线程调用
    }
}

// 或者使用 @Async + 事务同步器
@Async
public void asyncTask() {
    TransactionSynchronizationManager
        .registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                // 提交后执行
            }
        });
}

原则:事务是线程绑定的,跨线程事务必定失效。

7. LongAdder 比 AtomicLong 快,但你可能用错了

// ✅ 高并发场景:LongAdder 比 AtomicLong 性能好
LongAdder counter = new LongAdder();

// ✅ 普通并发场景:AtomicLong 足够,且能获取精确值
AtomicLong counter = new AtomicLong(0);

// ❌ 混淆场景:LongAdder 不能获取精确值
LongAdder stock = new LongAdder();  // 分布式场景下不能精确扣库存

// ✅ 需要精确值的场景用 AtomicLong 或数据库乐观锁
AtomicLong stock = new AtomicLong(100);
if (stock.decrementAndGet() >= 0) {
    // 下单成功
} else {
    stock.incrementAndGet();
    // 库存不足
}

选择原则:

  • 高并发 计数器(无精确值要求)→ LongAdder
  • 需要立即获取精确值 → AtomicLong
  • 分布式环境 → 数据库乐观锁 / Redis 原子操作

8. 死锁了都不知道,循环依赖锁

// ❌ 危险:转账场景死锁
@Service
public class TransferService {

    private final Map<String, BigDecimal> accounts = new ConcurrentHashMap<>();

    public void transfer(String from, String to, BigDecimal amount) {
        // 线程A: transfer(A, B)
        // 线程B: transfer(B, A)
        // 死锁!

        synchronized (accounts.get(from)) {
            synchronized (accounts.get(to)) {
                // 业务逻辑
            }
        }
    }
}

// ✅ 正确:统一加锁顺序
@Service
public class TransferService {

    private final Map<String, BigDecimal> accounts = new ConcurrentHashMap<>();

    public void transfer(String from, String to, BigDecimal amount) {
        // 按账户ID排序,确保加锁顺序一致
        String first = from.compareTo(to) < 0 ? from : to;
        String second = from.compareTo(to) < 0 ? to : from;

        synchronized (accounts.get(first)) {
            synchronized (accounts.get(second)) {
                // 业务逻辑
            }
        }
    }
}

// ✅ 更优方案:用 Redis 分布式锁
@Autowired
private RedissonClient redisson;

public void transfer(String from, String to, BigDecimal amount) {
    RLock lock1 = redisson.getLock("account:" + 
        (from.compareTo(to) < 0 ? from : to));
    RLock lock2 = redisson.getLock("account:" + 
        (from.compareTo(to) < 0 ? to : from));

    try {
        // 按顺序获取锁
        lock1.lock();
        lock2.lock();
        // 业务逻辑
    } finally {
        lock2.unlock();
        lock1.unlock();
    }
}

避坑速查表

检查项 操作
✅ synchronized 位置 锁资源,不锁方法
✅ 锁对象选择 final Object,禁止用装箱类型
✅ 并发集合 优先用 ConcurrentHashMap.compute/merge
✅ @Async 失效 同类内部调用要注入自身 Bean
✅ 线程池配置 核心数+队列+拒绝策略都要配
✅ 跨线程事务 事务链路不能断,子线程独立
✅ 原子变量选择 LongAdder 高并发,AtomicLong 需精确
✅ 死锁预防 统一加锁顺序,或用分布式锁

总结

并发问题,总结就3句话:

  1. 锁要锁对对象:synchronized 锁的是对象引用,不是代码
  2. 线程池必须配:核心数、队列容量、拒绝策略缺一不可
  3. 跨线程要小心:事务、锁的上下文都是线程绑定的

收藏这篇,下次遇到并发问题直接查,少踩90%的坑。




上一篇:亲历AI辅助诊断:我在三甲医院看偏头痛,真实数据告诉你准不准
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-5-6 03:15 , Processed in 0.627485 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表