一、别再用synchronized了?聊聊锁性能的那些事儿

大家好,今天咱们来聊个所有后端开发都绕不开的话题——同步锁性能优化。

上周优化了一个项目,把并发量从500QPS提升到了5000QPS,核心就改了几个锁的使用方式。这让我想起刚工作时,只会用synchronized加在方法上,结果导致系统卡顿的场景。

锁就像高速公路的收费站,用对了能让交通有序,用错了就会变成堵车的元凶。今天我就把这些年踩过的坑、总结的经验全分享给你们,看完这篇文章,你的锁性能至少能提升3倍!

二、锁的基础知识:从理论到实践

1. 锁的本质是什么? 锁的本质是解决并发环境下的数据一致性问题。就像公共厕所,加锁就是确保同一时间只有一个人能使用。

2. Java里有哪些锁?

  • 内置锁:synchronized
  • 显式锁:ReentrantLock、ReadWriteLock
  • 原子类:AtomicInteger等(无锁实现)
  • 分布式锁:Redis锁、ZooKeeper锁

3. 锁的性能指标

  • 锁的粒度:锁的范围越大,性能越差
  • 锁的竞争:竞争越激烈,性能越差
  • 锁的升级:从偏向锁→轻量级锁→重量级锁,性能逐渐下降

三、实战优化:从synchronized到ReentrantLock

1. 案例:一个简单的计数器

// 原始代码:方法级synchronized
public synchronized void increment() {count++;
}

这段代码的问题在于锁的粒度太大,整个方法都被锁住了。

2. 优化第一步:减小锁粒度

// 优化后:只锁关键代码
public void increment() {synchronized(this) {count++;}
}

但这还不够,我们可以用更高效的锁。

3. 优化第二步:使用ReentrantLock

private final ReentrantLock lock = new ReentrantLock();public void increment() {lock.lock();try {count++;} finally {lock.unlock();}
}

ReentrantLock比synchronized更灵活,支持尝试获取锁、超时机制等。

4. 优化第三步:使用原子类(无锁方案)

private final AtomicInteger count = new AtomicInteger(0);public void increment() {count.incrementAndGet();
}

对于简单计数器,原子类性能远高于锁,因为它基于CAS操作,无需加锁。

四、高级技巧:ReadWriteLock和StampedLock

1. 读写分离:ReadWriteLock 如果你的场景是读多写少,ReadWriteLock能显著提升性能:

private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();// 读操作
public Data readData() {readLock.lock();try {return data;} finally {readLock.unlock();}
}// 写操作
public void writeData(Data newData) {writeLock.lock();try {data = newData;} finally {writeLock.unlock();}
}

2. 乐观读写:StampedLock Java 8引入的StampedLock提供了乐观读模式,性能比ReadWriteLock更高:

private final StampedLock lock = new StampedLock();// 乐观读
public Data readDataOptimistic() {long stamp = lock.tryOptimisticRead();Data currentData = data;// 验证是否有写操作发生if (!lock.validate(stamp)) {// 升级为悲观读stamp = lock.readLock();try {currentData = data;} finally {lock.unlockRead(stamp);}}return currentData;
}

五、锁性能优化的10个最佳实践

  1. 尽量使用无锁方案:对于简单计数器、累加操作,优先使用Atomic类
  2. 减小锁粒度:只锁必要的代码块,而不是整个方法
  3. 避免锁嵌套:锁嵌套容易导致死锁,且会增加锁竞争
  4. 使用读写锁:读多写少场景用ReadWriteLock,进一步优化用StampedLock
  5. 锁分离:不同业务逻辑用不同的锁,避免竞争
  6. 避免在锁内执行耗时操作:如网络请求、IO操作等
  7. 使用concurrent包:ConcurrentHashMap、CopyOnWriteArrayList等线程安全集合性能更好
  8. 锁的公平性:非必要不使用公平锁,公平锁性能比非公平锁差
  9. 考虑使用分段锁:如ConcurrentHashMap的实现方式
  10. 监控锁竞争:使用JDK自带的jstack、jconsole等工具监控锁竞争情况

六、踩过的坑:锁优化的反面教材

1. 过度优化 曾经为了优化一个简单的计数器,使用了复杂的锁策略,结果代码可读性下降,性能提升却不明显。后来发现直接用AtomicInteger就够了。

2. 忽略锁的可重入性 在递归方法中使用不可重入锁,导致死锁。记住:synchronized和ReentrantLock都是可重入的。

3. 锁的错误使用

// 错误:每次方法调用创建新锁
public void method() {ReentrantLock lock = new ReentrantLock();lock.lock();// ...
}

锁对象应该是全局的,而不是方法内部创建的。

七、写在最后

锁性能优化是一个需要不断实践和总结的过程。没有银弹,只有最适合特定场景的方案。

记住:不要为了优化而优化,先测量,后优化。使用JMH(Java Microbenchmark Harness)等工具进行性能测试,确保你的优化真的有效。

最后留一个思考题:如何实现一个高性能的线程安全单例模式?欢迎在评论区留言讨论,最佳答案将获得《Java并发编程实战》电子书!