在开发中,并发问题是每个 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句话:
- 锁要锁对对象:synchronized 锁的是对象引用,不是代码
- 线程池必须配:核心数、队列容量、拒绝策略缺一不可
- 跨线程要小心:事务、锁的上下文都是线程绑定的
收藏这篇,下次遇到并发问题直接查,少踩90%的坑。