AQS类继承关系图
因为AQS类本身都是空方法,要使用必须自己去继承实现
AbstractOwnableSynchronizer类结构
AQS的父类,对独占模式持有同步锁进行定义、获取、设置
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/** Use serial ID even though all fields transient. */
private static final long serialVersionUID = 3737899427754241961L;
/**
* Empty constructor for use by subclasses.
*/
protected AbstractOwnableSynchronizer() { }
/**
* 独占模式同步的当前所有者线程
*/
private transient Thread exclusiveOwnerThread;
/**
* 设置 独占模式同步的当前所有者线程
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
/**
* 获取独占模式同步的当前所有者线程
*/
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
AbstractQueuedSynchronizer类结构
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private static final long serialVersionUID = 7373984972572414691L;
protected AbstractQueuedSynchronizer() { }
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上,当其他线程对Condition调用了signal()方法后,该节点从等待队列中转移到同步队列中,加入到对同步状态的获取中 */
static final int CONDITION = -2;
/**
* 表示下一个被默认对象的等待状态值应该无条件传播,就是共享模式需要对其它对象进行唤醒
*/
static final int PROPAGATE = -3;
/**
*1、CANCELLED,值为1 。场景:当该线程等待超时或者被中断,需要从同步队列中取消等待,则该线程被置1,即被取消(这里该线程在取消之前是等待状态)。节点进入了取消状态则不再变化;
*2、SIGNAL,值为-1。场景:后继的节点处于等待状态,当前节点的线程如果释放了同步状态或者被取消(当前节点状态置为-1),将会通知后继节点,使后继节点的线程得以运行;
*3、CONDITION,值为-2。场景:节点处于等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()方法后,该节点从等待队列中转移到同步队列中,
* 加入到对同步状态的获取中;
*4、PROPAGATE,值为-3。场景:表示下一次的共享状态会被无条件的传播下去;
*5、INITIAL,值为0,初始状态
*/
volatile int waitStatus;
/**
* 前驱节点,当节点加入同步队列的时候被设置(尾部添加)
*/
volatile Node prev;
/**
* 后继节点
*/
volatile Node next;
/**
* 获取同步状态的线程
*/
volatile Thread thread;
/**
* 等待节点的后继节点。如果当前节点是共享的,那么这个字段是一个SHARED常量,也就是说节点类型(独占和共享)和等待队列中的后继节点共用一个字段。
*(注:比如说当前节点A是共享的,那么它的这个字段是shared,也就是说在这个等待队列中,A节点的后继节点也是shared。如果A节点不是共享的,
*那么它的nextWaiter就不是一个SHARED常量,即是独占的。)
*/
Node nextWaiter;
/**
* 判断是否是共享节点.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回前一个节点,如果为空则抛出NullPointerException。当前任不能为空时使用。可以省略null检查,但它是用来帮助VM的。
*
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
//初始化node,如果是独占模式,初始化时nextWaiter会指向null,共享模式就指向SHARED
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
//指向头结点的指针
private transient volatile Node head;
//指向尾结点的指针
private transient volatile Node tail;
//状态,可以表示重入锁的重入次数
private volatile int state;
}
acquire(独占锁,获取资源)
//获取资源,如果tryAcquire没获取到资源,则acquireQueued从等待队列自旋去获取资源
//如果依然没获取到则进入阻塞状态,当然如果acquireQueued在判断取到的线程已经被中断
//则调用selfInterrupt去打断线程,当然你中断归中断,我抢锁还是照样抢锁,几乎没关系,只是我抢到锁了以
//后,设置线程的中断状态而已,也不抛出任何异常出来。调用者获取锁后,可以去检查是否发生过中断,也//可以不理会
//参数arg,该值会传送到{@link #tryAcquire},但否则不会被解释,可以代表任何内容,如reentrantLock传的是1。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//则调用selfInterrupt去打断线程,当然你中断归中断,我抢锁还是照样抢锁,几乎没关系,只是我抢到锁了以
//后,设置线程的中断状态而已,也不抛出任何异常出来。调用者获取锁后,可以去检查是否发生过中断,也//可以不理会
selfInterrupt();
}
- tryAcquire(独占)
尝试获取资源,这里只是进行定义,在继承AbstractQueuedSynchronizer类的类中重写
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
- addWaiter(为当前线程和给定模式创建和排队节点)
//添加或创建排队队列
//mode代表的是模式如:Node.EXCLUSIVE-独占模式,Node.SHARED-共享模式
private Node addWaiter(Node mode) {
//新建与一个当前线程关联的node
Node node = new Node(Thread.currentThread(), mode);
//新建节点的前指针指向队列中的尾节点
Node pred = tail;
//尾结点不为空的话
if (pred != null) {
//确定尾结点不为空后,新建节点的前指针指向尾结点
node.prev = pred;
//cas比较将新节点设置到队列尾结点
//根据当前对象AbstractQueuedSynchronized+tailOffset(尾结点的偏移量,也就是汇编中的寻址地址)找到内存中的Node节点
//然后用代码中的尾结点和找到内存的Node做比较,如果一样则将欲插入的节点更新当前内存中的尾结点
//保证线程安全
if (compareAndSetTail(pred, node)) {
//当前队列尾结点的next指向当前node,也就是将当前node加入等待队列
pred.next = node;
//返回当前新建节点
return node;
}
}
//如果cas比较设置尾结点失败后,则通过enq自旋直到加入成功为止
enq(node);
//加入成功后返回当前新建节点
return node;
}
- enq方法
//不断自旋,直到插入出错的节点入队列为止
private Node enq(final Node node) {
for (;;) {
//队列原尾结点
Node t = tail;
//尾结点为空,正常队列有头节点,尾结点就会指向头节点
//而尾节点为空,代表头结点应该也是不存在
//所以下方会新建节点去比较设置头节点
if (t == null) {
//创建新节点cas比较创建头结点
if (compareAndSetHead(new Node()))
//创建好头结点后,要让尾结点指向头结点
tail = head;
} else {
//当完成第一个if初始化后,则在这边加入新的节点到队列
//假如本来已经存在头结点,就会直接走else添加节点到队列
//将新节点前指针指向队列尾结点
node.prev = t;
//cas比较将新节点设置到队列尾结点
//根据当前对象AbstractQueuedSynchronized+tailOffset(尾结点的偏移量,也就是汇编中的寻址地址)找到内存中的Node节点
//然后用代码中的尾结点和找到内存的Node做比较,如果一样则将欲插入的节点更新当前内存中的尾结点
//保证线程安全
if (compareAndSetTail(t, node)) {
//将当前队列的尾结点的next指针指向欲插入的node节点
t.next = node;
//返回当前节点
return t;
}
}
}
}
- compareAndSetTail
//调用Unsafe类的cas方法比较设置尾结点
private final boolean compareAndSetTail(Node expect, Node update) {
//this代表当前的AbstractQueuedSynchronizer类对象
//tailOffset代表尾结点在内存中偏移量,也就是寻址地址
//expect是指期望的节点
//update是要更新的节点
//方法的意思是用当前对象加上尾结点的偏移量找到内存中的尾结点,然后用找到的尾结点和expect(期望的尾结点,或者是拿到的尾结点)节点作比较
//如果一样,就将当前要加入的新节点设置为新的尾结点并返回true
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
- acquireQueued自旋判断等待队列中的线程是不是可以获取资源,或者阻塞,或者中断
自旋获取资源,如果资源被占用,则挂起暂停执行
失败的线程则取消资源的获取
设置头结点
返回中断标志
//自旋获取资源,如果资源被占用,则挂起暂停执行
//失败的线程则取消资源的获取
//设置头结点
//返回中断标志
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//当前节点的前置节点
final Node p = node.predecessor();
//如果前置节点是头结点,老二可以尝试着获取资源
if (p == head && tryAcquire(arg)) {
//老二获取到资源后
//将自己设置为头节点
//将该节点在等待队列的线程置为空,释放当前线程
//将当前节点前一节点也置为空
setHead(node);
//前置节点的next指针也置为空,帮助GC
p.next = null;
failed = false;
//返回中断状态
return interrupted;
}
//老二没获取到资源,或者当前节点还不是老二
//则执行下面程序
//shouldParkAfterFailedAcquire对当前前置节点的状态检查是否是SIGNAL
//不是返回false,通过当前for循环去更新其前置节点并设置前置节点状态为SIGNAL
//当前置节点为SIGNAL
//则调用parkAndCheckInterrupt进行阻塞了,就停止在这句代码
//当前值节点释放资源后因为有SIGNAL状态,会去通知当前节点结束阻塞竞争资源
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//到这里中断状态改为true,当然只是改了下状态,并没有直接结束方法,而且第二再掉中断会变成false状态
//所以在selfInterrupt();要再进行一次中断记录这次是被中断的
interrupted = true;
}
} finally {
//如果获取失败,一般是中断抛出异常进入,这里基本不会
if (failed)
//取消资源的获取
cancelAcquire(node);
}
}
- setHead设置头结点
//将当前node设置为头结点
private void setHead(Node node) {
//将头节点指针指向当前节点,也就是当前节点设置为头节点
head = node;
//将该节点在等待队列的线程置为空,释放当前线程
node.thread = null;
//将当前节点前一节点也置为空
node.prev = null;
}
- shouldParkAfterFailedAcquire
这个方法主要是确定当前节点的前置节点是否是SIGNAL状态
如果前置节点取消了等待,则为他匹配新的前置节点
然后等下一次for循环调用shouldParkAfterFailedAcquire的时候确定前置节点置为SIGNAL再进入if (ws == Node.SIGNAL)返回true
//这个方法主要是确定当前节点的前置节点是否是SIGNAL状态
//如果前置节点取消了等待,则为他匹配新的前置节点
//然后等下一次for循环调用shouldParkAfterFailedAcquire的时候确定前置节点置为SIGNAL再进入if (ws == Node.SIGNAL)返回true
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取当前节点的前置节点的状态
//CANCELLED = 1;
//SIGNAL = -1;
//CONDITION = -2;
//PROPAGATE = -3;
int ws = pred.waitStatus;
//如果前置节点的状态已经是SIGNAL,也就是当前置节点释放资源的时候,要提醒当前节点去获取资源
if (ws == Node.SIGNAL)
//就直接返回true
return true;
//如果ws>0,则前置节点的状态CANCELLED,取消状态
//为当前节点重新匹配新的前置节点,因为取消的前置节点已经不会再去通知当前节点了
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果前置节点的状态不是取消的状态,则用cas将前一个节点设置为SIGNAL
//当前置节点释放资源的时候,要提醒当前节点去获取资源
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
}
- compareAndSetWaitStatus方法
//比较并设置等待状态值
private static final boolean compareAndSetWaitStatus(Node node,
int expect,
int update) {
//调用Unsafe的compareAndSwapInt方法比较并设置状态
//前一个node节点+等待状态的偏移量找到内存中的等待状态
//然后和现在前一个状态做比较,如果一样,可以对内存中的状态值用新值update进行替换
return unsafe.compareAndSwapInt(node, waitStatusOffset,
expect, update);
- parkAndCheckInterrupt线程挂起并检查是否中断
//当前一个方法shouldParkAfterFailedAcquire已经将前置节点设置为SIGNAL状态
//那当前节点就可以安心休息了
private final boolean parkAndCheckInterrupt() {
//线程挂起,阻塞在这
LockSupport.park(this);
//线程是否中断
return Thread.interrupted();
}
- LockSupport.park线程挂起,并记录线程阻塞对象和对应的偏移量
//线程挂起
public static void park(Object blocker) {
Thread t = Thread.currentThread();
//在调用park阻塞当前线程之前,先记录当前线程的blocker
setBlocker(t, blocker);
//调用park阻塞当前线程
//UNSAFE.park的两个参数,前一个为true的时候表示传入的是绝对时间,
//false表示相对时间,即从当前时间开始算。
//后面的long类型的参数就是等待的时间,0L表示永久等待
UNSAFE.park(false, 0L);
//阻塞结束后让线程继续执行下去的情况时,再将parkBlocker设置为null,
//因为当前线程已经没有被blocker住了,如果不设置为null,
//那诊断工具获取被阻塞的原因就是错误的,这也是为什么要有两个setBlocker的原因
setBlocker(t, null);
}
- cancelAcquire(取消节点获取资源)
//取消节点获取资源
private void cancelAcquire(Node node) {
// 忽略空节点
if (node == null)
return;
//将当前node维护的线程释放
node.thread = null;
// 跳过取消状态的前置节点,找到非取消状态的前置节点作为当前节点的前置节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
//新找到的前置节点的后置节点
Node predNext = pred.next;
//当前节点状态标注为取消状态
node.waitStatus = Node.CANCELLED;
// 当前节点已经是尾结点
//并且cas比较设置新的尾结点成功(新的尾结点就是当前节点的前置节点,也就是将当前node移除了)
if (node == tail && compareAndSetTail(node, pred)) {
//设置前置节点的下一个节点为空,到这一步已经将当前节点移除等待队列
compareAndSetNext(pred, predNext, null);
//如果不是尾结点或者cas比较没成功
} else {
int ws;
//前置节点不是头结点
//前置节点是SIGNAL或者(前置节点不是取消状态并且比较设置前置接的状态为SIGNAL成功)
//前置节点维护的线程没被释放
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
//进入这个if说明当前节点的前置不是头节点并且是SIGNAL状态,有维护线程
//当前节点的后继节点
Node next = node.next;
//后继节点存在并且不是cancel状态
if (next != null && next.waitStatus <= 0)
//直接比较设置前置节点的后继节点为当前节点的后继节点
//也就是移除了当前节点,让前置直接指向了后继节点
compareAndSetNext(pred, predNext, next);
//如果是头结点,或是别的状态进入else
} else {
//将当前节点状态设置为初始化状态0,释放当前节点的下一个节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
- compareAndSetTail(通过cas比较设置尾结点)
//当前AbstractQueuedSynchronizer类+尾结点偏移量找到内存中的尾结点
//用当前期望的尾结点和内存的尾结点比较,如果一样,将设置新的尾结点
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
- compareAndSetNext(通过cas比较设置next指针指向的节点)
/**
* 用当前node+下一个节点的偏移量得到内存中的节点,然后用期望节点和内存的节点进行比较,一样用update更新节点
*/
private static final boolean compareAndSetNext(Node node,
Node expect,
Node update) {
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
- unparkSuccessor取消线程的阻塞状态
当执行LockSupport.unpark(s.thread)方法后,该线程会去执行阻塞在 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())的parkAndCheckInterrupt方法下的其它代码,当唤醒了线程则它会去确认自己是不是老二,是的话就尝试获取锁,获取到了就将自己变成了头结点
//取消节点的阻塞状态
private void unparkSuccessor(Node node) {
//获取当前node的状态
int ws = node.waitStatus;
//node的状态不是取消状态则用cas比较设置状态为0初始化状态
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//node的后继节点
Node s = node.next;
//如果后继节点不存在或者后继节点已经是cancel放弃状态
if (s == null || s.waitStatus > 0) {
//如果s!=null,状态是cance状态则,将后继节点置空,帮助GC回收
s = null;
//从尾结点开始,尾结点不是空,一直往前遍历
//找到状态不是取消状态的节点,作为node的真正后继节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//后继节点不是空就释放后继线程
if (s != null)
LockSupport.unpark(s.thread);
}
acquireShared获取资源(共享锁)
//获取共享锁
public final void acquireShared(int arg) {
//获取到的状态>=0不会去执行获取资源(已经获取到资源),当然tryAcquireShared需要自己实现
if (tryAcquireShared(arg) < 0)
//获取共享锁
doAcquireShared(arg);
}
- doAcquireShared判断自己是老二嘛,是老二获取锁,释放资源后并通知其它共享节点,直到所有节点被唤醒
private void doAcquireShared(int arg) {
//获得一个共享模式与当前线程关联的节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//当前节点的前驱节点
final Node p = node.predecessor();
//前驱节点是头结点
if (p == head) {
//当前节点尝试获取资源
int r = tryAcquireShared(arg);
//获取到了资源
if (r >= 0) {
//将自己设为头结点
//并唤醒其它共享节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
//已经中断,则自我中断设置状态为true,记录本次中断状态
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
//老二没获取到资源,或者当前节点还不是老二
//则执行下面程序
//shouldParkAfterFailedAcquire对当前前置节点的状态检查是否是SIGNAL
//不是返回false,通过当前for循环去更新其前置节点并设置前置节点状态为SIGNAL
//当前置节点为SIGNAL
//则调用parkAndCheckInterrupt进行阻塞了,就停止在这句代码
//当前值节点释放资源后因为有SIGNAL状态,会去通知当前节点结束阻塞竞争资源
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//分析与独占的一样,就不多说了
if (failed)
cancelAcquire(node);
}
}
- setHeadAndPropagate设置头结点并且传递共享
//将自己设为头结点
//并唤醒其它共享节点
private void setHeadAndPropagate(Node node, int propagate) {
//当前头结点暂存
Node h = head;
//将当前节点设为新的头节点
setHead(node);
//propagate>0允许传播
//旧的头结点是空
//旧的头结点不是空但是旧的头结点的状态不是取消和初始化状态
//新的头结点是空
//新的头结点的状态不是取消和初始化状态
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
//当前节点的后继节点
Node s = node.next;
//后继节点是空或者后继节点是共享节点
if (s == null || s.isShared())
//唤醒其它共享节点
doReleaseShared();
}
}
- doReleaseShared更改自己的状态为PROPAGATE,并且唤醒其它共享节点
//判断自己是SIGNAL状态,是的话,有后继节点需要去唤醒
//调用unparkSuccessor唤醒它后继节点,唤醒后,后继节点从阻塞状态中醒来
//回到parkAndCheckInterrupt方法下的其它代码,去确认自己是不是老二了,
//是老二则去竞争老大竞争成功后,当然head改变了
//head变成自己的后继节点,然后又开始循环,直到没有其它需要唤醒的节点停止
private void doReleaseShared() {
for (;;) {
Node h = head;
//头结点存在,且后面还有节点,也就是队列里的节点不止一个
if (h != null && h != tail) {
int ws = h.waitStatus;
//头结点的状态是SIGNAL,说明释放它自己后需要去唤醒它的下一个节点
if (ws == Node.SIGNAL) {
//cas比较设置状态为0,因为要释放节点
//如果设置成功,执行unparkSuccessor唤醒下一个节点
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//当执行LockSupport.unpark(s.thread)方法后,
//该线程会去执行阻塞在
//if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
//的parkAndCheckInterrupt方法下的其它代码,
//当唤醒了线程则它会去确认自己是不是老二,是的话就尝试获取锁,
//获取到了就将自己变成了头结点
unparkSuccessor(h);
}
//当头结点的状态已经是初始化状态了,cas尝试将自己状态改完共享确保以后可以传递下去
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//头结点和当前节点一样,停止
//也就是头结点已经被其它人获取到了则还会继续循环,说明还有其它节点需要唤醒
//当头结点和h一样说明头结点的下一个节点并没有变成老大,也就是没有唤醒的了,结束
if (h == head)
break;
}
}
acquireSharedInterruptibly获取资源,可中断
//请求资源,可中断
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//是否已被中断,是的话就抛出异常直接返回了,不再执行方法
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取资源,没获取到就进入doAcquireSharedInterruptibly
if (tryAcquireShared(arg) < 0)
//获取资源,如果被状态则取消获取资源
doAcquireSharedInterruptibly(arg);
}
- interrupted
判断线程是否被中断,true代表会清除中断状态,假如第一次中断状态是true
但是第一次调用完isInterrupted(true)则会清除中断状态,第二次调用中断状态就变成了false
//判断线程是否被中断,true代表会清除中断状态,假如第一次中断状态是true
//但是第一次调用完isInterrupted(true)则会清除中断状态,第二次调用中断状态就变成了false
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
- 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;
}
}
//当前节点的前驱节点不是头结点,则找到正确的前驱节点并标注状态为SIGANAL
//然后进入阻塞状态
//当阻塞状态被唤醒并且是被中断的则会抛出异常,直接结束方法
//当然抛出异常后也会走finally,也就是failed默认true直接去取消获取资源
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
tryRelease(独占),需要重写
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
tryReleaseShared(共享),需要重写
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
理解的有啥不正确大家可以指出哦,喜欢的点个赞!!!!