ReentrantLock原理
基本用法
class Job{ private ReentrantLock lock = new ReentrantLock(); // 共享变量 private String data = null; /** * 临界方法 */ public void deal(){ // 加锁 lock.lock(); try { // TODO data = ""; } finally { // 释放 lock.unlock(); } } }
进入lock方法,调用的是sync的lock方法
public void lock() { sync.lock(); }
那么这个sync是什么呢?它是ReentrantLock的静态内部类,了解一下其继承结构
先从ReentrantLock里面的Sync及其子类开始
先简单说一下这三个类都实现了什么:
1. Sync提供了抽象方法lock,以供公平和非公平模式的子类实现。
2. 公平和非公平模式的子类需要实现AQS类里的tryAcquire方法,即尝试获取锁的具体实现。
3. 看其中的源码,我们还可以看到Sync类里还有个实现好的nonfairTryAcquire方法,那么问题来了,既然子类会完成tryAcquire方法的实现,为何还要多此一举?先看我画好的方法调用过程:
对于非公平锁
对于ReentrantLock里面的tryLock
对于公平锁
看完图,应该知道为什么在Sync里面实现nonfairTryAcquire了吧?
无论公平或者非公平模式,ReentrantLock的tryLock方法都会调用同一个方法nonfairTryAcquire,所以把这个方法放在父类Sync。也就能理解了作者的注释:
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/类似的,两种模式释放锁的逻辑也是一样的,所以把tryRelease也放在了父类中。说白了就是代码复用。
现在看下这三个类的源码
【Sync】
/** * 锁同步控制的基础。并在这个基础上实现了公平和非公平版本。 * 使用AQS的状态表示持有锁的数量。 */ abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; /** * 子类化的主要原因是允许非公平版本的快速途径。 */ abstract void lock(); /** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { // 当前线程 final Thread current = Thread.currentThread(); // 同步状态 int c = getState(); // 同步状态为0,代表锁没有被占用,这个时候可以获取 if (c == 0) { // CAS操作,如果内存值是预期值0,则用更新值acquires覆盖内存值,成功返回true if (compareAndSetState(0, acquires)) { // 设置当前拥有独占访问权的线程。 setExclusiveOwnerThread(current); // 锁获取成功 return true; } } // 如果当前线程已经持有这个锁了,那就重入 else if (current == getExclusiveOwnerThread()) { // 当前状态加上acquires(一般是+1) int nextc = c + acquires; // 异常情况 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); // 设置状态 setState(nextc); // 锁获取成功 return true; } // 没有获取到,直接返回false return false; } // 如果当前线程只获取一次,那么一进一出,同步状态为0,返回true;如果重入了几次,那么同步状态不一定减到0,返回就是false了。 protected final boolean tryRelease(int releases) { // 当前状态减去releases int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 如果同步状态为0,证明此时无人持有锁 if (c == 0) { // 只有在这种情况才是true free = true; // 没有线程拥有独占访问权 setExclusiveOwnerThread(null); } // 设置同步状态 setState(c); return free; } // 判断当前线程是否是拥有独占访问权的线程 protected final boolean isHeldExclusively() { // While we must in general read state before owner, // we don't need to do so to check if current thread is owner return getExclusiveOwnerThread() == Thread.currentThread(); } // new一个Condition final ConditionObject newCondition() { return new ConditionObject(); } // Methods relayed from outer class // 获取拥有独占访问权的线程 final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } // 获取重入次数 final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } // 同步状态为0即没有锁 final boolean isLocked() { return getState() != 0; } /** * Reconstitutes the instance from a stream (that is, deserializes it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state } }
【NonfairSync】
/** * 非公平锁的实现 */ static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; final void lock() { // 如果当前无人占用锁,并且当前线程设置同步状态成功,那么这个线程就“抢占”成功了(说白了就是不管有没有等待很久的线程) if (compareAndSetState(0, 1)) // 设置拥有独占访问权的线程 setExclusiveOwnerThread(Thread.currentThread()); else // 如果没有抢成功,则调用AQS的acquire方法(进而进入当前类的tryAcquire方法,非公平实现) acquire(1); } protected final boolean tryAcquire(int acquires) { // 尝试获取,调用的是父类的非公平实现 return nonfairTryAcquire(acquires); } }
【FairSync】
/** * 公平锁的实现 */ static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { // 调用AQS的acquire方法(进而进入当前类的tryAcquire方法,公平实现) acquire(1); } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 区别在这里,先判断是否有人在等待,如果有就返回false;没有的话即尝试设置同步状态抢占锁 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; } }
很简单的逻辑
1. 锁空闲则尝试设置同步状态以抢占。
2. 重入的情况,更新同步状态即可。
3. 公平锁仅仅多了个判断当前等待队列是否为空。
4. CAS:比较并交换,原子操作,在一定程度上提高了程序效率。但是也有个ABA问题,如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。
可重入的作用
1. 锁的作用是同一时间内只有一个线程能访问共享资源
2. 当A拿到了锁,证明这个锁被持有了,其它线程等待
3. 如果是可重入锁,那么线程A拿到了doSomething的对象锁后,可以继续调用同一个对象的其它方法
class Funny { public synchronized void doSomething(){ doNothing(); } public synchronized void doNothing(){ // TODO } }
4. 如果不可重入,当进入方法后,想执行doNothing,但是此时对象锁已经被持有,那么这个方法将等待自己释放锁,也就永远获取不到doNothing的锁,造成死锁
一切源于AbstractQueuedSynchronizer(AQS)
内部结构大体可以分为两部分:CLH同步队列和Condition条件。
根据CLH队列的Node结构来看,可以看出两种模式:共享模式和独占模式。(问题来了:共享模式和独占模式的作用是什么?为什么要搞两种?)
独占模式:在一个线程持有锁的前提下,其他线程试图获取该锁将无法成功。
共享模式:锁可能(但不一定)被多个线程共同持有。
具体应用体现:ReentrantLock中是独占模式;CountDownLatch中是共享模式;ReentrantReadWriteLock中的ReadLock是共享模式,WriteLock是独占模式(表示可以多个线程同时读,只能一个线程写)
我们知道Condition是线程通信的工具,那么Condition是怎样影响CLH队列的?这个在后面慢慢解释。
先熟悉一下基础内容Node
/** * AQS是“CLH”(发明人是Craig、Landin和Hagersten)锁队列的变体。 * CLH锁通常用于自旋锁。但是这里使用相同的基本策略来实现同步。 */ static final class Node { /** 节点在共享模式下等待的标记 */ static final Node SHARED = new Node(); /** 节点在独占模式下等待的标记 */ static final Node EXCLUSIVE = null; /** 表示线程已取消 */ static final int CANCELLED = 1; /** 表示后继线程需要被阻塞.(用于独占锁) */ static final int SIGNAL = -1; /** 表示线程在Condition条件上阻塞等待 */ static final int CONDITION = -2; /** 表示下一个acquireShared应该无条件传播。(用于共享锁) */ static final int PROPAGATE = -3; /** * SIGNAL: 此节点的后续节点被(或将要)阻塞(通过park),因此当前节点在释放或取消时必须取消其后续节点。 * 为了避免争用,acquire方法必须首先表明它们需要一个信号,然后原子的重试acquire方法,如果失败,则阻塞。 * * CANCELLED: 此节点由于超时或中断节点从未离开此状态而被取消。特别是,具有已取消节点的线程将不再阻塞。 * * CONDITION: 此节点当前位于条件队列上。在传输之前,它不会被用作同步队列节点,此时状态将被设置为0。 * * PROPAGATE: 一个被释放的节点应该被传播到其他节点。这是在doReleaseShared中设置的(仅针对head节点),以确保传播能够继续,即使其他操作已经介入。 * * 0: None of the above * * 值以数字形式排列以简化使用。非负值意味着节点不需要发出信号。因此,大多数代码不需要检查特定的值,只需检查符号。 * * 对于正常的同步节点,字段初始化为0 */ volatile int waitStatus; /** * 当前节点/线程的前驱节点。 */ volatile Node prev; /** * 当前节点/线程的后继节点。 */ volatile Node next; /** * 当前节点拥有的线程 */ volatile Thread thread; /** * 如果等于SHARED,则当前节点是共享模式。 * 否则是独占模式,这个值也是Condition队列的下一个节点。 */ Node nextWaiter; /** * 如果当前节点以共享模式等待,返回true。 */ final boolean isShared() { return nextWaiter == SHARED; } /** * 返回前驱节点,如果为空则抛出空指针异常。 */ final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } Node() { // 用于创建链表head或共享模式下的SHARED } Node(Thread thread, Node mode) { // addWaiter方法中使用 this.nextWaiter = mode; this.thread = thread; } Node(Thread thread, int waitStatus) { // Condition类中使用 this.waitStatus = waitStatus; this.thread = thread; } }
另外还有AQS的成员变量
/** * 等待队列的头指针 */ private transient volatile Node head; /** * 等待队列的尾指针,仅仅通过enq(入队)方法添加新节点的方式来修改。 */ private transient volatile Node tail; /** * 同步状态(很多地方用到这个状态,比如CountDownLatch利用state当做计数器,而这里代表是否有线程持有锁以及重入次数) */ private volatile int state;
独占模式
独占模式有四个主要方法
1. acquire
2. acquireInterruptibly
3. tryAcquireNanos
4. release
1. acquire(int arg)
// 以独占模式获取,忽略中断 public final void acquire(int arg) { // 如果tryAcquire返回false获取锁失败(tryAcquire是交给子类来实现的) if (!tryAcquire(arg) && // 则把当前线程放入等待队列 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 中断线程??? selfInterrupt(); } static void selfInterrupt() { // 这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。Thread.interrupt()方法不会中断一个正在运行的线程。 Thread.currentThread().interrupt(); }
思路很简单,尝试获取锁,获取不到则等待。注意点是interrupt,见方法注释。
addWaiter方法,这里是以独占模式作为参数的
// 给当前线程以给定的模式创建并入队一个新节点 private Node addWaiter(Node mode) { // 新Node Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; // 如果队列已经创建,就将新节点插入队尾。 if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 如果队列没有创建,通过enq方法创建队列,并插入新的节点。 enq(node); return node; } // 入队方法 private Node enq(final Node node) { for (;;) { Node t = tail; // 初始化 if (t == null) { // Must initialize // 利用CAS设置头结点 if (compareAndSetHead(new Node())) // 尾指针指向头结点 tail = head; } else { // 新节点的前驱指向尾节点 node.prev = t; // 设置新节点为新的tail if (compareAndSetTail(t, node)) { // 原尾节点的next指向新尾节点(新Node) t.next = node; // 返回原尾节点 return t; } } } }
关于入队过程
添加完等待节点之后,执行acquireQueued方法
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { // 找到当前节点的前驱 final Node p = node.predecessor(); // 如果前驱是头结点,则执行tryAcquire尝试获取锁 if (p == head && tryAcquire(arg)) { // 获取锁成功,设置当前节点为头结点 setHead(node); // 原头结点的next置空,这样下次垃圾回收的时候就会回收原头结点 p.next = null; // help GC // 在finally里,因为这里是false,则不会执行cancelAcquire方法 failed = false; // 这里返回false,就不会执行acquire方法里的selfInterrupt()方法了 return interrupted; } // 不满足上述条件:1. 判断是否应该阻塞。2. 应该阻塞的话,则阻塞 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) // 发生异常的话,取消获取锁的尝试,将当前Node的等待状态设置为CANCELLED cancelAcquire(node); } } private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 前一个节点的等待状态 int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * 这个节点已经设置了请求释放信号的状态,所以它可以安全地阻塞 */ return true; if (ws > 0) { /* * 前一个节点是取消状态。跳过前驱并重试。 */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * 等待状态是 0 或者 PROPAGATE。表示需要个信号但不要阻塞。调用者需要重试,以确保在阻塞之前不能获取到锁。 */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } private final boolean parkAndCheckInterrupt() { // 为线程调度的目的禁用当前线程,除非许可证可用 LockSupport.park(this); return Thread.interrupted(); } // java.util.concurrent.locks.LockSupport#park(java.lang.Object) // LockSupport是JDK中的底层类,用来创建锁和其他同步工具类的基本线程阻塞原语。 // 通过调用LockSupport.park()和LockSupport.unpark()可以实现线程的阻塞和唤醒。 // LockSupport提供了1个许可证供使用,如果这个许可还没有被占用,当前线程获取许可并继续执行;如果许可已经被占用,当前线程阻塞,等待获取许可。 public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); }
cancelAcquire方法
private void cancelAcquire(Node node) { // Ignore if node doesn't exist if (node == null) return; node.thread = null; // 跳过取消状态的阶段 Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; // predNext is the apparent node to unsplice. CASes below will // fail if not, in which case, we lost race vs another cancel // or signal, so no further action is necessary. Node predNext = pred.next; // Can use unconditional write instead of CAS here. // After this atomic step, other Nodes can skip past us. // Before, we are free of interference from other threads. node.waitStatus = Node.CANCELLED; // If we are the tail, remove ourselves. if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { // If successor needs signal, try to set pred's next-link // so it will get one. Otherwise wake it up to propagate. int ws; if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { Node next = node.next; if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { unparkSuccessor(node); } node.next = node; // help GC } }
2. acquireInterruptibly(int arg)
如果锁不被其他线程持有,则获取锁并立即返回,将锁持有计数设置为1。
如果当前线程已经持有该锁,那么持有计数将增加1,该方法立即返回。(一)如果锁是由另一个线程持有,那么当前线程就会出于线程调度的目的而被禁用,并处于休眠状态,直到发生以下两种情况之一:
1. 锁被当前线程获取;
2. 其他线程中断当前线程。(二)如果锁被当前线程获取,那么锁持有计数被设置为1。如果当前线程:
1. 在进入此方法时已设置其中断状态
2. 在获取锁时被中断
则抛出 InterruptedException,并清除当前线程的中断状态。在这个实现中,由于这个方法是一个显式的中断点,所以优先响应中断而不是正常的或可重入的锁获取。
// 以独占模式获取,如果中断则中止。 public final void acquireInterruptibly(int arg) throws InterruptedException { // 如果当前线程已中断,则抛出异常 if (Thread.interrupted()) throw new InterruptedException(); // 如果尝试获取锁失败,则抛出异常终止 if (!tryAcquire(arg)) doAcquireInterruptibly(arg); }
doAcquireInterruptibly方法
private void doAcquireInterruptibly(int arg) throws InterruptedException { // 把当前线程添加到等待队列,方法实现前面已经说过 final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { // 找到当前节点的前驱 final Node p = node.predecessor(); // 如果前驱是头结点,并且tryAcquire尝试获取锁成功 if (p == head && tryAcquire(arg)) { // 设置当前节点为头结点 setHead(node); p.next = null; // help GC failed = false; return; } // 不满足上述条件:1. 判断是否应该阻塞。2. 应该阻塞的话,则阻塞。 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // 3. 响应中断,抛出中断异常 throw new InterruptedException(); } } finally { if (failed) // 发生异常的话,取消获取锁的尝试,将当前Node的等待状态设置为CANCELLED cancelAcquire(node); } }
3. tryAcquireNanos(int arg, long nanosTimeout)
如果在给定的等待时间内另一个线程没有持有锁,并且当前线程不是中断状态,则获取锁,并立即返回值{@code true},将锁持有计数设置为1。
如果当前线程已经持有该锁,那么持有计数将增加1,并且该方法返回{@code true}。如果这个锁被设置为使用公平的排序策略,那么如果任何其他线程正在等待这个锁,那么将不会获得一个可用的锁。
(一)如果锁是由另一个线程持有,那么当前线程就会出于线程调度的目的而被禁用,并处于休眠状态,直到发生以下三种情况之一:
1. 锁被当前线程获取
2. 其他线程中断当前线程
3. 指定的等待时间已经过了
(二)如果获取了锁,则返回值{@code true},并将锁持有计数设置为1。如果当前线程:
1. 在进入此方法时已设置其中断状态
2. 在获取锁时被中断
则抛出 InterruptedException,并清除当前线程的中断状态。
如果指定的等待时间过期,则返回值{@code false}。如果时间小于或等于0,则该方法根本不会等待。在这个实现中,由于这个方法是一个显式的中断点,所以优先响应中断,而不是正常的或可重入的获取锁,或者报告等待时间的流逝。
// 尝试以独占模式获取,如果中断则中止,如果给定超时时间超时则失败。 public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { // 优先响应中断 if (Thread.interrupted()) throw new InterruptedException(); // 尝试获取锁,成功返回true;如果tryAcquire返回false,则执行doAcquireNanos,在指定时间内尝试获取 return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); }
doAcquireNanos方法:
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { // 如果过期时间小于等于0,直接返回 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(); // 如果前驱是head并且尝试获取锁成功,则把当前节点设置为头结点并将当前头结点的next置空,方便回收。 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return true; } // 判断是否超时,超时则直接返回 nanosTimeout = deadline - System.nanoTime(); if (nanosTimeout <= 0L) return false; // 没有超时,则判断是否应该阻塞以及判断超时时间是否大于指定的自旋时间 if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) // 如果nanosTimeout大于0,则阻塞 LockSupport.parkNanos(this, nanosTimeout); // 响应中断 if (Thread.interrupted()) throw new InterruptedException(); } } finally { if (failed) // 发生异常则把当前线程设置为取消状态 cancelAcquire(node); } }
4. release(int arg)
如果当前线程是这个锁的持有者,那么持有计数将递减。如果持有计数现在为零,则释放锁。
// 以独占模式释放 public final boolean release(int arg) { // 尝试释放锁 if (tryRelease(arg)) { Node h = head; // 如果头结点不为空并且等待状态不为0 if (h != null && h.waitStatus != 0) // 唤醒头结点的后继 unparkSuccessor(h); return true; } return false; }
unparkSuccessor方法
/** * 唤醒节点的后继 */ private void unparkSuccessor(Node node) { // 如果当前节点的状态是负数(比如:-1是等待信号通知的状态),则尝试清理预期的信号状态。 int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 将要被取消阻塞的通常只是下一个节点。 Node s = node.next; // 如果后继节点为空或者状态为cancelled,也就是1 if (s == null || s.waitStatus > 0) { s = null; // 则从tail向前遍历,以找到非取消状态的节点。 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } // 将最终确定的线程节点唤醒 if (s != null) LockSupport.unpark(s.thread); }
共享模式
共享模式涉及4个方法:
1. acquireShared
2. acquireSharedInterruptibly
3. tryAcquireSharedNanos
4. releaseShared
1. acquireShared(int arg)
// 以共享模式获取,忽略中断 public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
doAcquireShared方法
private void doAcquireShared(int arg) { // 以共享模式创建一个新Node,添加到等待队列里 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { boolean interrupted = false; for (;;) { // 当前Node的前驱 final Node p = node.predecessor(); // 如果前驱是head if (p == head) { // 尝试获取共享锁 int r = tryAcquireShared(arg); // 获取成功 if (r >= 0) { // 设置头结点并传播 setHeadAndPropagate(node, r); p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } // 判断是否需要阻塞 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } /** * 设置队列的头部并检查后继节点是否在共享模式下等待,如果是,并且propagate > 0或者已设置为PROPAGATE状态,则传播。 * @param node the node * @param propagate the return value from a tryAcquireShared */ private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below setHead(node); /* * 尝试通知下一个节点如果: * 1. Propagation 由调用者表示, * 或者被之前的操作记录下来(在setHead方法之前或者之后设置为h.waitStatus) * (注意:这将使用等待状态的信号检查,因为PROPAGATE状态可能会转换为SIGNAL。) * * 2. 下一个节点在共享模式下等待,或者我们不知道,因为它看起来是空的 * */ if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; if (s == null || s.isShared()) doReleaseShared(); } }
2. acquireSharedInterruptibly(int arg)
// 以共享模式获取,如果中断则中止 public final void acquireSharedInterruptibly(int arg) throws InterruptedException { // 优先响应中断 if (Thread.interrupted()) throw new InterruptedException(); // 如果尝试获取失败,调用doAcquireSharedInterruptibly方法 if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
doAcquireSharedInterruptibly方法
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { // 以共享模式添加到等待队列 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { // 当前节点的前驱 final Node p = node.predecessor(); // 如果前驱是头结点 if (p == head) { // 尝试获取共享锁 int r = tryAcquireShared(arg); if (r >= 0) { // 成功获取到则传播状态 setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } // 前驱不是头结点,则检查是否需要阻塞,阻塞之后如果被中断则抛出中断异常 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
3. tryAcquireSharedNanos(int arg, long nanosTimeout)
// 尝试以共享模式获取,如果中断则中止,如果给定超时时间超时则失败 public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { // 优先响应中断 if (Thread.interrupted()) throw new InterruptedException(); // 尝试获取共享锁 return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout); }
doAcquireSharedNanos方法
private boolean doAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { // 如果超时时间小于等于0,直接返回 if (nanosTimeout <= 0L) return false; final long deadline = System.nanoTime() + nanosTimeout; // 以共享模式添加到等待队列 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { // 获取当前节点的前驱 final Node p = node.predecessor(); // 如果前驱是头结点 if (p == head) { // 尝试获取共享锁 int r = tryAcquireShared(arg); if (r >= 0) { // 获取成功,设置当前节点为头结点,并唤醒下一个等待的线程 setHeadAndPropagate(node, r); p.next = null; // help GC 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); } }
4. releaseShared(int arg)
// 以共享模式释放 public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
doReleaseShared方法
private void doReleaseShared() { // 确保一个释放传播,即使有其他正在进行的acquires/releases。 // 如果需要信号的话,按照正常的方式试图唤醒head的后继。 // 如果不需要信号,则将状态设置为PROPAGATE,以确保在释放后传播仍将继续。 // 此外,我们必须循环,以防在执行此操作时添加新节点。 // 另外,与其它使用unparkSuccessor方法不同,这里如果CAS重置状态失败,将重新检查 for (;;) { Node h = head; // 从头结点开始,如果当前节点不为空并且不是tail if (h != null && h != tail) { // 当前节点状态 int ws = h.waitStatus; // 如果是等待通知的状态 if (ws == Node.SIGNAL) { // 重置状态,失败则重新循环 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases // 重置成功,唤醒后继线程 unparkSuccessor(h); } // 如果状态是0,则设置状态为PROPAGATE。设置失败重新循环 else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }
独占模式和共享模式对比:
ReentrantLock方法到AQS方法的调用关系:
ReentrantLock#lock------------------------------------------------------------AbstractQueuedSynchronizer#acquire
ReentrantLock#lockInterruptibly--------------------------------------------AbstractQueuedSynchronizer#acquireInterruptibly
ReentrantLock#tryLock(long, java.util.concurrent.TimeUnit)--------AbstractQueuedSynchronizer#tryAcquireNanos
ReentrantLock#unlock--------------------------------------------------------AbstractQueuedSynchronizer#releaseReentrantLock#tryLock-------------------------------------------------------ReentrantLock.Sync#nonfairTryAcquire
需要子类实现的方法
// 独占模式下尝试获取锁
java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire
// 独占模式下尝试释放锁
java.util.concurrent.locks.AbstractQueuedSynchronizer#tryRelease
// 共享模式下尝试获取锁
java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquireShared
// 共享模式下尝试释放锁
java.util.concurrent.locks.AbstractQueuedSynchronizer#tryReleaseShared
// 判断持有锁的是否是当前线程
java.util.concurrent.locks.AbstractQueuedSynchronizer#isHeldExclusively
关于AQS大概工作原理:
ReentrantLock利用AQS实现了公平/非公平锁。
AQS维护了一个volatile的同步状态state和一个存储等待线程的双向队列,以及提供了独占和共享两种模式的抽象方法。ReentrantLock是独占锁,CountDownLatch则是共享锁的实现。
参与竞争的线程利用CAS设置state,设置同步状态失败的线程都会被封装成Node加入到等待队列并在队列中自旋,移出队列的条件是前驱节点为头节点且成功获取了同步状态。
head是虚节点,有机会争用锁的是head指向结点的后继,也就是队列中第二个结点,如果成功获取锁,这个结点会成为新的头结点。
获取锁失败的线程会被阻塞,这得益于 LockSupport.park 方法,提供了基本线程阻塞原语,会让当前线程挂起到WAITING状态。被挂起的线程会等待 LockSupport.unpark 方法来唤醒,通俗的讲unpark方法提供了“许可”,告诉线程你可以活动了,不过这种许可是一次性的。具体为:每个线程都有个permit开关,park方法会将permit设置为0,unpark方法会将permit设置成1。
在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后用 LockSupport.unpark 唤醒头节点的后继节点。
.