1. 为什么我们需要Lock锁
在Java并发编程的世界里,synchronized关键字可能是大多数开发者最先接触的同步机制。但当我真正处理高并发生产环境的问题时,发现synchronized存在几个致命缺陷:无法中断一个正在等待获取锁的线程、无法尝试获取锁(获取不到立即返回)、无法设置超时时间。这些限制在复杂的业务场景下简直就是灾难。
Lock接口的出现完美解决了这些问题。记得去年我们电商系统在大促时,就因为一个synchronized导致的死锁问题损失了上百万。后来全面重构为ReentrantLock后,不仅解决了死锁问题,还通过tryLock()实现了优雅降级。
2. Lock锁的核心实现解析
2.1 AQS - Lock的基石
AbstractQueuedSynchronizer(AQS)是Lock实现的核心。它维护了一个volatile int state(代表锁的状态)和一个FIFO线程等待队列。当我第一次阅读ReentrantLock源码时,发现其公平锁和非公平锁的实现差异就在于是直接尝试获取锁还是先检查队列。
// 非公平锁获取逻辑 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 重入逻辑 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }2.2 可重入性设计
Lock的可重入设计与synchronized类似,但实现更透明。每次重入state值+1,释放时-1,直到state=0才真正释放锁。这里有个坑我踩过:务必保证lock()和unlock()成对出现,最好用try-finally包裹:
Lock lock = new ReentrantLock(); lock.lock(); try { // 临界区代码 } finally { lock.unlock(); // 必须放在finally中 }3. 高级特性实战应用
3.1 条件变量(Condition)
Condition的await()/signal()比Object的wait()/notify()更灵活。在我们的订单系统中,用不同的Condition分别管理支付超时和库存释放:
class OrderService { private final Lock lock = new ReentrantLock(); private final Condition paymentTimeout = lock.newCondition(); private final Condition stockRelease = lock.newCondition(); void cancelOrder() { lock.lock(); try { paymentTimeout.await(30, TimeUnit.SECONDS); stockRelease.signalAll(); } finally { lock.unlock(); } } }3.2 读写锁优化
我们的配置中心使用ReadWriteLock实现了超高并发的配置读取:
class ConfigCenter { private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); private final Lock readLock = rwLock.readLock(); private final Lock writeLock = rwLock.writeLock(); String getConfig(String key) { readLock.lock(); try { // 读取操作 } finally { readLock.unlock(); } } void updateConfig(String key, String value) { writeLock.lock(); try { // 写操作 } finally { writeLock.unlock(); } } }4. 性能对比与选型建议
4.1 基准测试数据
在我们压力测试环境下(16核32G,JDK17):
| 场景 | synchronized | ReentrantLock | 差异 |
|---|---|---|---|
| 单线程重入 | 12ns | 16ns | +33% |
| 4线程竞争 | 142ns | 98ns | -31% |
| 16线程高竞争 | 2.4ms | 1.7ms | -29% |
| 条件变量通知 | 56μs | 32μs | -43% |
4.2 选型决策树
根据多年经验总结的选择策略:
- 简单同步 -> synchronized
- 需要超时/中断 -> ReentrantLock
- 读多写少 -> ReadWriteLock
- 分布式环境 -> 考虑Redisson等分布式锁
5. 生产环境踩坑实录
5.1 死锁检测
JDK自带的死锁检测工具很多时候不够用。我们开发了一套运行时死锁检测系统,核心原理是定时dump线程栈分析:
ThreadMXBean bean = ManagementFactory.getThreadMXBean(); long[] threadIds = bean.findDeadlockedThreads(); if (threadIds != null) { ThreadInfo[] infos = bean.getThreadInfo(threadIds); // 告警逻辑 }5.2 锁粒度优化
曾经有个商品查询接口,有人直接在方法上加了synchronized。优化后使用ConcurrentHashMap分段锁,QPS从200提升到8000+:
class ProductService { private final Map<String, Lock> segmentLocks = new ConcurrentHashMap<>(); Product getProduct(String id) { Lock lock = segmentLocks.computeIfAbsent(id, k -> new ReentrantLock()); lock.lock(); try { // 查询逻辑 } finally { lock.unlock(); } } }6. 最佳实践与代码规范
- 锁的可见性:所有锁对象必须声明为final
- 锁命名:给锁加上有意义的名称(JDK16+支持)
- 避免嵌套锁:严格控制锁的嵌套层次
- 超时设置:永远为锁操作设置超时时间
- 监控指标:暴露锁等待时间等JMX指标
// 好的锁使用示例 class PaymentService { private final Lock paymentLock = new ReentrantLock(true); // 公平锁 boolean processPayment() { if (!paymentLock.tryLock(100, TimeUnit.MILLISECONDS)) { metrics.counter("lock.timeout").increment(); return false; } try { // 支付逻辑 } finally { paymentLock.unlock(); } } }7. 常见问题排查指南
问题1:锁未被释放
- 现象:线程池逐渐耗尽
- 排查:jstack查看线程状态,搜索"parking to wait for"
- 修复:确保所有路径都有unlock()
问题2:锁竞争激烈
- 现象:CPU不高但吞吐量低
- 排查:Arthas监控lock.getQueueLength()
- 修复:减小锁粒度或改用读写锁
问题3:死锁
- 现象:服务完全卡死
- 排查:jstack或ThreadMXBean检测
- 修复:统一锁获取顺序,使用tryLock
8. 与其它并发工具配合
8.1 结合StampedLock
在GPS轨迹处理中,我们使用StampedLock优化了地理位置查询:
class LocationTracker { private final StampedLock lock = new StampedLock(); void updatePosition(double x, double y) { long stamp = lock.writeLock(); try { // 更新逻辑 } finally { lock.unlockWrite(stamp); } } double distanceFromOrigin() { long stamp = lock.tryOptimisticRead(); // 读取操作 if (!lock.validate(stamp)) { stamp = lock.readLock(); try { // 重新读取 } finally { lock.unlockRead(stamp); } } } }8.2 与CompletableFuture集成
异步任务中的锁使用需要特别注意:
CompletableFuture.supplyAsync(() -> { if (lock.tryLock()) { try { // 异步操作 } finally { lock.unlock(); } } return null; });9. JVM层实现原理
HotSpot虚拟机中,Lock的实现依赖于:
- CAS操作:通过Unsafe类实现
- 内存屏障:保证可见性和有序性
- 线程调度:与操作系统mutex交互
通过-XX:+PrintAssembly可以看到lock指令生成的汇编代码。在x86架构下,最终会转换为cmpxchg指令。
10. 未来发展趋势
Project Loom的虚拟线程对锁的影响:
- 同步锁会pin住载体线程
- 建议使用ReentrantLock而非synchronized
- 新的锁实现可能出现在JDK21+
在异步编程场景下,响应式编程的锁使用范式完全不同,需要采用原子变量或无锁数据结构。