别再只用synchronized了!手把手教你用ReentrantLock和Condition优化Java并发代码
解锁Java并发新姿势ReentrantLock与Condition实战指南如果你还在用synchronized解决所有并发问题可能已经错过了Java并发编程中最强大的武器库。本文将带你突破内置锁的限制掌握ReentrantLock和Condition这对黄金组合实现从能用到优雅高效的跨越。1. 为什么我们需要超越synchronized在Java并发编程的早期阶段synchronized确实是解决线程安全问题的银弹。但随着应用复杂度提升它的局限性逐渐暴露// 典型synchronized方法 public synchronized void transfer(Account from, Account to, int amount) { // 转账逻辑 }这种写法虽然简单但存在几个致命缺陷无法中断等待一旦线程开始等待锁就只能傻等到底单一等待条件所有等待线程都在同一个等待队列无法精细控制非公平锁默认可能导致线程饥饿现象缺乏超时机制容易引发死锁风险ReentrantLock的出现解决了这些痛点它提供了可中断的锁获取机制公平锁与非公平锁可选尝试获取锁的超时能力多条件变量支持实际案例某电商平台在秒杀场景下使用synchronized导致大量线程阻塞改用ReentrantLock的tryLock超时机制后系统吞吐量提升了40%。2. ReentrantLock核心机制解析2.1 基础用法对比先看一个计数器案例的两种实现// synchronized版本 public class SyncCounter { private int count; public synchronized void increment() { count; } } // ReentrantLock版本 public class LockCounter { private int count; private final Lock lock new ReentrantLock(); public void increment() { lock.lock(); // 手动获取锁 try { count; } finally { lock.unlock(); // 必须手动释放 } } }关键区别特性synchronizedReentrantLock锁获取方式自动手动锁释放自动必须手动可中断否是公平锁非公平可配置条件变量单一多个性能JDK6后优化更灵活2.2 高级特性实战可中断锁获取public void interruptibleTask() throws InterruptedException { Lock lock new ReentrantLock(); try { lock.lockInterruptibly(); // 可被中断的锁获取 // 执行关键代码 } finally { lock.unlock(); } }尝试锁与超时if (lock.tryLock(1, TimeUnit.SECONDS)) { // 等待1秒 try { // 获取锁成功 } finally { lock.unlock(); } } else { // 超时处理逻辑 }公平锁设置// 创建公平锁按申请顺序获取 Lock fairLock new ReentrantLock(true);注意公平锁会降低吞吐量仅在严格顺序要求时使用3. Condition实现精准线程调度synchronized的wait/notify机制存在虚假唤醒和无条件竞争问题。Condition提供了更精细的控制class BoundedBuffer { final Lock lock new ReentrantLock(); final Condition notFull lock.newCondition(); // 条件1 final Condition notEmpty lock.newCondition(); // 条件2 // 缓冲区操作... public void put(Object x) throws InterruptedException { lock.lock(); try { while (buffer.isFull()) notFull.await(); // 等待不满条件 buffer.add(x); notEmpty.signal(); // 唤醒不空等待者 } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (buffer.isEmpty()) notEmpty.await(); // 等待不空条件 Object x buffer.remove(); notFull.signal(); // 唤醒不满等待者 return x; } finally { lock.unlock(); } } }这种设计实现了生产者只唤醒消费者notEmpty消费者只唤醒生产者notFull避免无效的线程唤醒竞争4. 复杂场景实战订单处理系统假设我们需要实现一个订单处理系统要求多线程处理订单高优先级订单优先处理处理超时自动放弃系统关闭时优雅终止public class OrderProcessor { private final Lock lock new ReentrantLock(); private final Condition hasOrders lock.newCondition(); private final PriorityQueueOrder queue new PriorityQueue(); private volatile boolean shutdown false; public void addOrder(Order order) { lock.lock(); try { queue.offer(order); hasOrders.signal(); } finally { lock.unlock(); } } public void processOrders() throws InterruptedException { while (!shutdown) { lock.lockInterruptibly(); try { while (queue.isEmpty() !shutdown) { hasOrders.await(100, TimeUnit.MILLIS); // 定期检查关闭 } Order order queue.poll(); if (order ! null) { if (!processWithTimeout(order, 1, TimeUnit.SECONDS)) { log.warn(Order {} timeout, order.id); } } } finally { lock.unlock(); } } } public void shutdown() { shutdown true; lock.lock(); try { hasOrders.signalAll(); // 唤醒所有工作线程 } finally { lock.unlock(); } } private boolean processWithTimeout(Order order, long timeout, TimeUnit unit) { // 带超时的订单处理逻辑 } }这个实现展示了多个高级特性可中断的锁获取带超时的条件等待优雅关闭机制优先级队列支持5. 性能优化与最佳实践5.1 锁分段技术对于高竞争场景可以采用锁分段提升并发度class StripedCounter { private final int segments 16; private final Lock[] locks new ReentrantLock[segments]; private final int[] counts new int[segments]; public StripedCounter() { for (int i 0; i segments; i) { locks[i] new ReentrantLock(); } } public void increment() { int hash Thread.currentThread().hashCode() % segments; locks[hash].lock(); try { counts[hash]; } finally { locks[hash].unlock(); } } public int getTotal() { int sum 0; for (int i 0; i segments; i) { locks[i].lock(); try { sum counts[i]; } finally { locks[i].unlock(); } } return sum; } }5.2 避免常见陷阱忘记释放锁始终在finally块中释放锁过度使用公平锁公平锁会显著降低吞吐量条件等待不使用循环防止虚假唤醒锁粒度过大只锁定必要的代码段5.3 监控与调试技巧使用ThreadMXBean监控锁竞争ThreadMXBean bean ManagementFactory.getThreadMXBean(); long[] threadIds bean.findDeadlockedThreads(); if (threadIds ! null) { ThreadInfo[] infos bean.getThreadInfo(threadIds); for (ThreadInfo info : infos) { System.out.println(info.getLockName()); } }6. 现代并发工具的选择策略虽然ReentrantLock强大但Java并发包还提供了其他选择场景推荐工具优势简单同步synchronized简洁、自动管理复杂条件等待ReentrantLockCondition精细控制、多条件读多写少ReadWriteLock读写分离、提升并发度并发集合ConcurrentHashMap内置线程安全、高性能异步任务CompletableFuture函数式编程、组合操作在实际项目中我通常会遵循以下决策流程首先考虑synchronized是否足够需要更灵活控制时选择ReentrantLock读多写少场景使用ReadWriteLock集合操作优先考虑并发容器复杂流程使用更高级的并发工具记住没有最好的锁只有最适合场景的锁。理解每种工具的特性才能写出既安全又高效的并发代码。