1. 为什么需要更强大的锁机制在Java多线程开发中synchronized关键字可能是大多数开发者最先接触的同步工具。记得我刚工作那会儿处理线程安全问题就是无脑加synchronized直到有次线上系统出现死锁排查了整整两天才发现问题所在。synchronized确实简单易用但在复杂的并发场景下就显得力不从心了。举个例子假设我们有个银行转账系统用synchronized实现大概长这样public class BankAccount { private double balance; public synchronized void transfer(BankAccount to, double amount) { this.balance - amount; to.balance amount; } }这种实现有个致命问题当A给B转账的同时B也给A转账就可能出现死锁。更麻烦的是一旦发生死锁synchronized既不能中断也不能设置超时系统就直接卡死了。这就是为什么我们需要更强大的锁机制——ReentrantLock。ReentrantLock提供了synchronized不具备的三大能力可中断锁等待锁的线程可以被中断超时机制可以设置获取锁的等待时间公平锁可以按申请锁的顺序获取锁在实际项目中我曾经用ReentrantLock重构过一个订单处理系统。原先用synchronized时高峰期经常出现线程堆积改用ReentrantLock的tryLock(500, TimeUnit.MILLISECONDS)后系统稳定性明显提升超时订单能够自动放弃处理而不是一直阻塞。2. ReentrantLock的核心特性解析2.1 基础锁功能对比先看一个最简单的计数器实现我们用三种方式分别实现// 不加锁版本 public class NonLockCounter { private int count; public void add() { count; // 非原子操作 } } // synchronized版本 public class SyncCounter { private int count; public synchronized void add() { count; } } // ReentrantLock版本 public class LockCounter { private int count; private final Lock lock new ReentrantLock(); public void add() { lock.lock(); try { count; } finally { lock.unlock(); } } }测试时开100个线程各执行100次add操作不加锁版本的结果每次都不一样而synchronized和ReentrantLock版本都能稳定输出10000。看起来效果一样但ReentrantLock的优势在于更灵活的控制。2.2 可中断与超时机制这是ReentrantLock最实用的特性之一。假设我们有个需要获取多个资源的操作public class ResourceManager { private final Lock lock1 new ReentrantLock(); private final Lock lock2 new ReentrantLock(); public boolean transferWithTimeout(long timeout, TimeUnit unit) throws InterruptedException { long stopTime System.nanoTime() unit.toNanos(timeout); // 尝试获取第一个锁 if (!lock1.tryLock(timeout, unit)) { return false; } try { // 计算剩余时间 long remaining stopTime - System.nanoTime(); // 尝试获取第二个锁 if (!lock2.tryLock(remaining, TimeUnit.NANOSECONDS)) { lock1.unlock(); // 记得释放已获取的锁 return false; } try { // 执行业务逻辑 return doTransfer(); } finally { lock2.unlock(); } } finally { lock1.unlock(); } } }这种写法完美避免了死锁风险我在实际项目中处理分布式锁时经常使用这种模式。相比之下synchronized一旦开始等待就只能死等没有任何回旋余地。2.3 公平锁与非公平锁ReentrantLock的另一个强大之处是可以创建公平锁Lock fairLock new ReentrantLock(true); // true表示公平锁公平锁会按照线程请求锁的顺序来分配锁避免了线程饥饿问题。不过要注意公平锁的性能通常比非公平锁低因为要维护请求队列。根据我的测试在高并发场景下非公平锁的吞吐量能比公平锁高出5-10倍。3. Condition的精准线程控制3.1 基本等待/通知机制Condition可以理解为ReentrantLock的等待/通知机制类似于Object的wait/notify但更灵活。先看个生产者消费者例子public class MessageQueue { private final Lock lock new ReentrantLock(); private final Condition notFull lock.newCondition(); private final Condition notEmpty lock.newCondition(); private final Object[] items new Object[100]; private int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count items.length) { notFull.await(); // 队列满时等待 } items[putptr] x; if (putptr items.length) putptr 0; count; notEmpty.signal(); // 通知消费者 } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count 0) { notEmpty.await(); // 队列空时等待 } Object x items[takeptr]; if (takeptr items.length) takeptr 0; count--; notFull.signal(); // 通知生产者 return x; } finally { lock.unlock(); } } }这个实现比用wait/notify更清晰而且可以创建多个Condition实现更精细的控制。我在一个日志处理系统中就用过这种模式不同的Condition分别处理不同优先级的日志。3.2 多Condition应用场景Condition最强大的地方在于一个Lock可以创建多个Condition。比如实现一个线程安全的连接池public class ConnectionPool { private final Lock lock new ReentrantLock(); private final Condition hasAvailable lock.newCondition(); private final Condition canReturn lock.newCondition(); private final int maxSize; private final SetConnection pool new HashSet(); private int activeCount 0; public Connection get() throws InterruptedException { lock.lock(); try { while (activeCount maxSize) { hasAvailable.await(); } Connection conn createConnection(); activeCount; canReturn.signal(); return conn; } finally { lock.unlock(); } } public void release(Connection conn) { lock.lock(); try { while (!pool.contains(conn)) { canReturn.await(); } pool.remove(conn); activeCount--; hasAvailable.signal(); } finally { lock.unlock(); } } }这种设计可以避免虚假唤醒问题而且不同状态的等待可以分开处理代码逻辑更清晰。4. 实战中的选择与优化4.1 何时选择ReentrantLock根据我的经验以下场景适合使用ReentrantLock需要可中断的锁获取操作需要超时控制的锁获取需要公平锁特性需要多个条件变量(Condition)需要尝试获取锁(tryLock)而简单场景下synchronized仍然是更好的选择因为语法更简洁JVM会优化synchronized的性能自动释放锁不会忘记解锁4.2 性能优化技巧在使用ReentrantLock时有几个性能优化点需要注意锁粒度尽量减小锁的代码块范围锁分离读写锁分离(ReentrantReadWriteLock)锁降级写锁降级为读锁避免锁嵌套容易导致死锁我曾经优化过一个商品库存系统将原来的大锁拆分为多个细粒度锁后QPS从200提升到了1500。关键代码片段public class Inventory { private final MapLong, Item items new HashMap(); private final MapLong, ReentrantLock locks new ConcurrentHashMap(); public void updateStock(long itemId, int delta) { ReentrantLock lock locks.computeIfAbsent(itemId, k - new ReentrantLock()); lock.lock(); try { Item item items.get(itemId); item.setStock(item.getStock() delta); } finally { lock.unlock(); } } }4.3 常见坑与解决方案在使用ReentrantLock时我踩过不少坑这里分享几个典型问题坑1忘记释放锁lock.lock(); try { // 业务代码 if(someCondition) { return; // 这里直接return会导致锁未释放 } } finally { lock.unlock(); // 必须放在finally块 }坑2锁重入次数不匹配public void methodA() { lock.lock(); try { methodB(); // 内部也调用了lock.lock() } finally { lock.unlock(); // 需要调用unlock()次数与lock()次数相同 } }坑3Condition使用不当Condition condition lock.newCondition(); // 错误必须先获取锁才能调用await() condition.await(); // 正确用法 lock.lock(); try { condition.await(); } finally { lock.unlock(); }这些坑我都真实踩过特别是第一个问题曾经导致我们线上系统出现严重的线程阻塞。后来我们制定了代码规范要求所有锁操作必须用try-finally包裹并在Code Review时重点检查。