JAVA并发之ReentrantLock原理解析( 二 )

AQS(AbstractQueueSynchronizer)AQS是一个基于队列的同步器,它是一个抽象类,主要提供了多线程获取锁时候的排队等待和激活机制,ReentrantLock内部有两个基于AQS实现的子类,分别针对公平锁和非公平锁做了支持 。
下面我们以公平锁为例,讲解下ReentrantLock是如何依赖AQS实现其功能的 。获得锁涉及到的主要源代码和解释如下:
//AQS源码,公平锁的lock()方法会直接调用该方法//这里当前如果获取失败会调用acquireQueued方法//addWaiter方法主要是将当前线程加入AQS内部队列的尾部public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}//ReentrantLock中实现公平锁的AQS子类的方法protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();//c == 0表示当前AQS为初始状态,可以尝试获取锁, 如获取成功则返回trueif (c == 0) {if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}// 只有当前线程是已经获取了该锁的线程才能再次获取锁(可重入锁)并返回trueelse if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}//返回false获取失败return false;}//AQS源码final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();//这里如果当前线程对应的队列里的Node的前置Node是head,则尝试获取锁,并成功返回if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}//shouldParkAfterFailedAcquire方法标记当前线程Node的前置Node的waitStatus为SIGNAL,意思是当你从队列移除时记得要唤醒我哦//parkAndCheckInterrupt方法会让当前线程挂起,停止自旋,免得白白浪费CPU资源if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}假设现在有线程A、B、C同时去获取同一把锁,大概的流程是这样的,这里假设线程A获的锁并成功返回,线程B和C则依次调用上面的方法,进入AQS的等待队列并最终被挂起 。

JAVA并发之ReentrantLock原理解析

文章插图
 
这时线程A做完自己该做的事情了,它在finally块中调用了unlock方法,这时我们再看下相关的源代码 。
//AQS源码,当前线程在释放锁的同时,会判断它所在Node的waitStatus有没有被它的后继结点标记,如果被标记了,那就唤醒后继结点对应的线程public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}//AQS源码,主要关注最下面LockSupport.unpark(s.thread),唤醒后继结点线程private void unparkSuccessor(Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling.It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);/** Thread to unpark is held in successor, which is normally* just the next node.But if cancelled or Apparently null,* traverse backwards from tail to find the actual* non-cancelled successor.*/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);}线程A释放锁时,会唤醒head结点的后继结点也就是线程B,此时线程B就可以继续for循环里面自旋并成功获得锁了 。
unsafe相关之前介绍AtomicInteger的时候提到Unsafe对象,AtomicInteger用到了Unsafe对象的CAS功能(底层是cpu提供的功能) 。
ReentrantLock除了用到了CAS之外,还用到了Unsafe的pack和unpack两个方法(LockSupport当中),除了性能更好之外,它可以精确的唤醒某一个线程 。

【JAVA并发之ReentrantLock原理解析】


推荐阅读