ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深入解析ReentrantLock与AQS的并发机制

深入解析ReentrantLock与AQS的并发机制 1. ReentrantLock与AQS的关系解析在JDK并发包中ReentrantLock是最常用的显式锁实现它的核心功能完全依赖于AbstractQueuedSynchronizerAQS框架。理解这两者的关系是掌握Java并发编程的关键。ReentrantLock内部通过Sync这个内部类继承AQS来实现锁机制。Sync又有两个子类FairSync公平锁和NonfairSync非公平锁分别对应不同的锁获取策略。这种设计体现了模板方法模式——AQS负责线程排队、阻塞/唤醒等底层机制而具体锁的实现只需关注状态管理。关键点AQS通过state变量volatile int表示锁状态0表示未锁定0表示被锁定。exclusiveOwnerThread记录当前持有锁的线程实现可重入特性。2. 核心数据结构与线程排队机制2.1 CLH队列变体AQS使用CLH锁的变体来实现线程排队。与原始CLH不同AQS的队列是双向链表每个节点Node包含static final class Node { volatile int waitStatus; volatile Node prev; volatile Node next; volatile Thread thread; Node nextWaiter; // 用于Condition队列 }waitStatus有几种关键状态SIGNAL(-1)后继节点需要被唤醒CANCELLED(1)线程已取消CONDITION(-2)节点在条件队列中2.2 队列操作细节当线程获取锁失败时会执行以下入队操作创建Node并设置当前线程快速尝试通过CAS添加到队尾如果失败进入enq()自旋直到成功private Node enq(final Node node) { for (;;) { Node t tail; if (t null) { // 必须初始化 if (compareAndSetHead(new Node())) tail head; } else { node.prev t; if (compareAndSetTail(t, node)) { t.next node; return t; } } } }这个设计保证了在高并发下也能正确构建队列。特别注意新节点总是先设置prev指针再CAS更新tail这是为了避免从前往后遍历时丢失节点。3. 锁获取流程深度剖析3.1 公平锁与非公平锁差异公平锁的tryAcquire实现protected final boolean tryAcquire(int acquires) { final Thread current Thread.currentThread(); int c getState(); if (c 0) { if (!hasQueuedPredecessors() // 关键区别检查是否有排队线程 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; }非公平锁会直接尝试CAS获取锁不检查队列情况。这可能导致插队现象但能提高吞吐量。3.2 完整获取流程tryAcquire尝试获取锁失败后调用addWaiter将线程包装为Node并入队acquireQueued中自旋尝试获取锁获取失败后调用shouldParkAfterFailedAcquire设置前驱节点的waitStatus为SIGNAL最后调用parkAndCheckInterrupt阻塞线程关键方法shouldParkAfterFailedAcquire的处理逻辑前驱节点已是SIGNAL可直接阻塞前驱节点状态0CANCELLED跳过这些节点其他情况CAS设置前驱为SIGNAL4. 锁释放与线程唤醒机制4.1 释放锁的核心步骤public final boolean release(int arg) { if (tryRelease(arg)) { // 子类实现 Node h head; if (h ! null h.waitStatus ! 0) unparkSuccessor(h); // 唤醒后继节点 return true; } return false; }tryRelease的实现要点检查当前线程是否是锁持有者计算新的state值原state - releases如果state0设置exclusiveOwnerThread为null4.2 唤醒线程的精妙设计unparkSuccessor方法有几个关键设计从尾向前遍历避免并发修改导致的节点丢失问题清除已取消的节点提高后续遍历效率实际唤醒的是head节点的下一个有效节点private void unparkSuccessor(Node node) { int ws node.waitStatus; if (ws 0) compareAndSetWaitStatus(node, ws, 0); Node s node.next; if (s null || s.waitStatus 0) { s null; for (Node t tail; t ! null t ! node; t t.prev) if (t.waitStatus 0) s t; } if (s ! null) LockSupport.unpark(s.thread); }5. 可中断与超时机制实现5.1 可中断获取锁与不可中断模式的主要区别在于检查中断状态后立即抛出InterruptedException取消节点时处理更严格private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node addWaiter(Node.EXCLUSIVE); boolean failed true; try { for (;;) { final Node p node.predecessor(); if (p head tryAcquire(arg)) { setHead(node); p.next null; // help GC failed false; return; } if (shouldParkAfterFailedAcquire(p, node) parkAndCheckInterrupt()) throw new InterruptedException(); // 关键区别 } } finally { if (failed) cancelAcquire(node); } }5.2 超时控制实现tryLock(long timeout, TimeUnit unit)的核心在于计算绝对deadline时间自旋时检查剩余时间使用parkNanos进行精确时间阻塞private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (nanosTimeout 0L) return false; final long deadline System.nanoTime() nanosTimeout; final Node node addWaiter(Node.EXCLUSIVE); boolean failed true; try { for (;;) { final Node p node.predecessor(); if (p head tryAcquire(arg)) { setHead(node); p.next null; failed false; return true; } nanosTimeout deadline - System.nanoTime(); if (nanosTimeout 0L) return false; if (shouldParkAfterFailedAcquire(p, node) nanosTimeout spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }6. 实际应用中的经验与陷阱6.1 性能优化启示非公平锁的吞吐量通常比公平锁高20%~30%短任务场景使用非公平锁长任务考虑公平锁合理设置超时时间避免线程饥饿6.2 常见问题排查问题1锁无法释放检查是否成对调用lock/unlock确保unlock在finally块中检查重入次数是否匹配问题2CPU占用过高可能是大量线程在自旋获取锁考虑使用tryLock减少阻塞评估锁粒度是否过粗问题3死锁使用jstack检查锁持有情况考虑使用超时获取锁遵循固定的锁获取顺序6.3 自定义锁实现建议基于AQS实现自定义锁时明确锁特性可重入公平共享正确实现tryAcquire/tryRelease考虑中断和超时支持进行充分的多线程测试示例一个简单的不可重入锁实现class SimpleLock extends AbstractQueuedSynchronizer { protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; } public void lock() { acquire(1); } public void unlock() { release(1); } }理解ReentrantLock和AQS的实现原理不仅能帮助我们更好地使用这些工具还能在遇到并发问题时快速定位原因。AQS的精妙设计体现了Doug Lea对并发编程的深刻理解其代码值得反复研读。
返回列表