AQS为锁提供了一系类的模板方法,结合队列管理,结合Lock接口的实现 我们针对这些模板方法做一些说明,分为独占模式和共享模式的方法,本文主要讲Lock的实现,但涉及到AQS的机制,看本文之前需要先了解AQS的工作机制
Lock接口介绍
Lock接口的方法,具体功能的实现是依靠子类的实现
public interface Lock {
// 获取锁,调用该方法时当前线程将会获取锁,当获得锁之后将会从该方法返回
void lock();
// 可中断获取锁,和lock()不同在于该方法会响应中断,在锁的获取中可以中断当前线程获取锁操作
void lockInterruptibly() throws InterruptedException;
// 尝试非阻塞的获取锁,调用该方法后立即返回,如果能够获取则返回true,否则返回false
boolean tryLock();
// 超时的获取锁,当线程出现以下3中情况时返回:
// 1. 当前线程在超时时间内获得锁
// 2. 当前线程在超时时间内被中断
// 3. 超时时间结束,返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 获取等待通知的组建,该组建和当前的锁绑定,当前线程只有获得了锁
// 才能调用该组件的wait()方法,调用后,当前线程将释放锁
Condition newCondition();
}
实现Lock接口的常见的有 独占模式的有重入锁ReentrantLock,写锁WriteLock。共享模式的有读锁ReadLock,
独占模式
已ReentrantLock的非公平锁为例
lock()
// 步骤1 调用Lock方法
public void lock() {
sync.lock();
}
// 步骤2 调用Sync的lock方法,ReentrantLock两种模式公平非公平,这里用非公平的展示
final void lock() {
if (compareAndSetState(0, 1))//cas操作成功 就设置独占线程,不管队 非公平体现
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//进入AQS方法
}
acquire(1)方法,AQS的模板方法,将state管理 和 队列结合起来
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取写锁。失败则入队自旋挂起
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
进入acquireQueued方法才是获取锁的核心方法了,也是AQS的模板方法
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); // 成功了 设置当前节点为头节点
p.next = null; // help GC
failed = false;
return interrupted;
}
//上述判断条件不成立,进入判断是否需要挂起线程操作 如需要挂起线程进入 &&后的方法
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //如果进入到这个方法里,那代码就停留在这里了
interrupted = true;
}
} finally {
if (failed)//正常情况failed = false 除非方法执行异常了,failed还是true才执行这里
cancelAcquire(node);
}
}
被挂起的线程就停在parkAndCheckInterrupt那里,等待被唤醒,理论上lock方法会获取到锁,只是时间长短问题
void lockInterruptibly() throws InterruptedException
该方法 没有返回值,但是抛出中断异常,说明可以响应异常
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);//AQS方法
}
acquireInterruptibly方法默认参数1

public final void acquireInterruptibly(int arg)throws InterruptedException {
if (Thread.interrupted()) // 检查当前线程是否中断 如果是直接抛出中断异常
throw new InterruptedException();
if (!tryAcquire(arg))//尝试获取资源:`tryAcquire(arg)`,如果成功,则直接返回。
doAcquireInterruptibly(arg);
}
步骤1 先判断是否中断,是直接抛出中断异常,到这里也就结束了,中断意思是中断线程某种操作,这里就中断了线程尝试获取锁的操作
线程运行--》调用lock系列方法尝试获取锁--》拿不到锁进入队列挂起 等待拿到锁--》拿到锁继续执行
在上面第二步如果中断了,就不往后了,线程原来该怎么运行还怎么运行,不接受入队啊锁什么的事
步骤2 再次尝试获取锁,拿不到在开始接受AQS的入队以及其他 管理 doAcquireInterruptibly方法
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);
}
}
这个方法和上面的acquireQueued大体一致,区别就在acquireQueued不响应中断,而该方法响应异常
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//线程挂起 停留在这行不执行了
throw new InterruptedException();
}
在parkAndCheckInterrupt方法里面做了 挂起线程操作 LockSupport.park(this);而对应的解挂线程的操作有LockSupport.unpark(this.thread),另外一种方式就是响应中断,很好理解,挂起线程是一种操作,在此之前设置中断标识意思 我要中断接下来的挂起操作,让你挂起操作芭比Keio
所以lockInterruptibly和lock方法区别就在于
lock方法 优先考虑获取锁 不可中断,会等待直到获得锁,当获得锁之后将会从该方法返回
lockInterruptibly优先考虑 响应中断,即使没有拿到锁也会因为中断而退出
boolean tryLock()
首先 这个方法是个布尔值类型的 返回的是ture或者false
public boolean tryLock( ) {
return sync.nonfairTryAcquire();//这里没有使用AQS模板方法
}
我们还是看非公平nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 判断独占锁是否当前没有线程持有
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current); //设置独占线程结束
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//如果是当前线程 重入
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
这个方法实现比较简单的,独占锁就是判断当前是否允许获得获得,获得就更新设置独占线程,或者如果当前线程以及持有锁就累加1,如果不允许就返回false
该方法不涉及到什么入队管理等待什么,尝试获取锁,试一下呗,成功了拿到锁返回ture,失败了就返回false。有枣没枣打一杆子,成就成不成就不成呗,会立即给你个明确的结果
boolean tryLock(long time, TimeUnit unit) throws InterruptedException
这个 方法返回的也是一个布尔类型
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));//AQS方法
}
tryAcquireNanos
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException(); //抛出中断异常 说明可以响应中断
return tryAcquire(arg) || // 尝试获取锁失败 进入后面doAcquireNanos方法
doAcquireNanos(arg, nanosTimeout);
}
doAcquireNano方法
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; // 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);
}
}
这个方法和acquireQueued方法很相似,多了三个东西
1.截止时间比较 nanosTimeout
2.规定时间的挂起线程 LockSupport.parkNanos(this, nanosTimeout)
3.响应中断
在上面我们讲了 线程的挂起就停留在那里不在执行了,等待后续的唤醒,而挂起线程的操作是线程park方法LockSupport.park(this),解挂方式两种1.unPark方法精准线程解挂 2.中断解挂
而这里挂起线程 LockSupport.parkNanos(this, nanosTimeout),除了上面两种解挂方式之外,该方法还定义了时间,到时间自动解挂
该方法作用:超时的获取锁,当线程出现以下3中情况时返回:
- 当前线程在超时时间内获得锁 返回ture
- 当前线程在超时时间内被中断 返回false
- 超时时间结束,返回false
由于nanosTimeout 设定是纳秒单位,所以也是很快就会给返回拿到锁 或者没有拿到锁的结果,只不过区别与tryLock方法的是,多了 规定时间内 和响应中断,不果如果线程在这里没有拿到锁,线程该执行还继续执行,当前线程加入队列的节点会触发finally 代码块清除掉
void unlock()
上面几个都是获取锁的方法,而对应的就是锁的释放方法
public void unlock() {
sync.release(1);//还是AQS方法
}
release方法
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(arg)
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//判断当前线程是不是独占线程 不是就GG,不允许释放锁 抛出异常结束
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
//判断锁是否释放干净了 这考虑了重入锁多次情况,有时候一次释放不干净
free = true;
setExclusiveOwnerThread(null);//清空独占线程
}
setState(c);
return free;
}
独占模式 下 同一时刻只有一个线程运行 并且设置成独占线程,在此基础上CAS操作state值,这是独占模式的核心设定。并且释放独占锁 必须当前线程持有独占锁的情况下进行,保证state值的原子性
步骤2 unparkSuccessor(h);唤醒后续节点,在我们lock系列方法里面的各种park挂起线程嘛,这里就是unpark了,唤醒后续节点线程让他动起来,然后在执行更新头节点等操作,到这里就闭环了
Condition newCondition()
这个比较多 单独介绍
Condition 介绍描述
共享模式
我们已ReentrantReadWriteLock.ReadLock做说明## void lock()
void lock()
读锁获取方法
public void lock() {
sync.acquireShared(1); // 调用 AQS 的共享模式获取锁
}
AQS 的acquireShared()方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)//tryAcquireShared()具体实现由 Sync 类提供
doAcquireShared(arg);
}
ReentrantReadWriteLock.Sync.tryAcquireShared方法,该方法上面已经讲过了,它会给出一个明确的结果尝试获取读锁或者失败,如果获取是否返回-1,则进入doAcquireShared方法,该方法是模板方法,入队 自旋判断 挂起 等待唤醒
void lockInterruptibly() throws InterruptedException
2.可中断读锁获取:lockInterruptibly()
该方法是实现Lock接口方法,核心在于可响应中断,即中断获取读锁的操作
public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);//调用 AQS 的共享中断模式获取锁
}
acquireSharedInterruptibly的方法和上面AQS 的acquireShared()方法区别就在于响应中断
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//判断是否有中断,有则响应中断抛出中断异常 方法就停止 即中断获取读锁的操作
if (Thread.interrupted())throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
doAcquireSharedInterruptibly方法和上面的doAcquireShared区别在于中断抛出异常
boolean tryLock()
重写的Lock方法,尝试非阻塞的获取锁,调用该方法后立即返回,如果能够获取则返回true,否则返回false
public boolean tryLock() {
return sync.tryReadLock();//具体实现由Sync提供
}
sync.tryReadLock()
final boolean tryReadLock() {
//当前线程
Thread current = Thread.currentThread();
for (;;) {
//自旋 直到成功或明确失败退出 这点和Sync里读锁的获取方法设计理念一致
int c = getState();
//检查写锁状态 如果当前有写锁并且是被别的线程持有 直接失败退出
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
return false;
//走到这里 意味着没有线程持有写锁 或者写锁被当前线程持有 后者情况 锁降级
int r = sharedCount(c);
//获取读锁计数 判断是否超限 如果超直接抛出错误退出
if (r == MAX_COUNT) throw new Error("Maximum lock count exceeded");
// CAS 更新读锁计数,如果CAS失败,说明有线程竞争导致CAS失败:继续循环重试(自旋)
if (compareAndSetState(c, c + SHARED_UNIT)) {
//CAS操作成功 就已经获取到读锁了 进行后续更新本地线程记录
//先处理firstReader 判断是否首次获取读锁
if (r == 0) {
//是首次获取读锁 设置当前线程为首次获取读锁线程
firstReader = current;
// 设置首次获取读锁计数值为1
firstReaderHoldCount = 1;
}
// 如果r 不为0,那么判断 当前线程是否是首个读锁持有者
else if (firstReader == current) {
// 是 重入 计数值+1
firstReaderHoldCount++;
}
// 既不是首次获取读锁 当前线程也不算首次获取读锁线程 则从缓存线程对象找
else {
// 获取缓存对象
HoldCounter rh = cachedHoldCounter;
// 判断缓存对象是否有效并且是当前线程的缓存
if (rh == null || rh.tid != getThreadId(current))
//不是有效缓存或者不是当前线程的缓存 从ThreadLocal本地线程变量集合里拿
cachedHoldCounter = rh = readHolds.get();
//缓存对象计数值是否归0了,0则重置ThreadLocal(缓存计数归0需要清理资源)
else if (rh.count == 0)readHolds.set(rh);
//存储线程本地重入次数+1
rh.count++;
}
return true;
}
}
总结:
①tryReadLock()方法和Sync类tryAcquireShared方法的前半段一致,只是没有兜底方法
②tryReadLock()方法,非阻塞特性:如果写锁已经被其他线程占用,则不会阻塞当前线程,而是会立即返回flase
,使用自旋方式主要在于CAS操作失败重试,存在多个线程竞争CAS操作更新state值,如果CAS操作失败则自旋重试, 这里自旋的次数取决于竞争线程的数量,理论上也很快就可以返回结果,如果能够获取则返回true,否则返回false
③与lock方法区别
在于 Lock会入队挂起线程 直到唤醒才会继续自旋执行 直到获取到锁,理论上Lock方法一定会获取到锁,只是时间长短问题
,而tryLock不涉及到入队线程不存在挂起,如果CAS操作失败也会自旋 但取决于竞争线程数量,这个会很快的,理论上tryLock方法会立即返回结果值明确成功获取锁还是失败,tryLock方法不一定能获取到锁
boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
重写lock接口的方法,作用:超时的尝试获取锁
//首先该方法抛出`InterruptedException`,说明该方法可以响应中断
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
//调用Sync方法实现 参数1尝试获取锁数量,参数timeout指定超时时间 单位纳秒
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));通过Sync继承AQS,是AQS模板方法实现
//AQS模板方法
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
//抛出中断异常 说明可以响应中断异常
if (Thread.interrupted())throw new InterruptedException();
//调用两个方法Sync.tryAcquireShared 和 doAcquireSharedNanos(arg, nanosTimeout)
return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout);
}
使用Sync.tryAcquireShared 方法尝试快速获取锁,立即返回结果值,返回值1代表获取锁 返回-1代表获取锁失败,值得注意的是tryAcquireShared(arg)是非阻塞的 如果写锁被其他线程持有直接返回-1,后续如果CAS操作失败 则进入兜底方法重试直到成功返回1
当tryAcquireShared(arg) >= 0 返回false 意味着没有获取到锁 进入doAcquireSharedNanos方法,这个方法特点在于 1 可响应中断 2超时自动解挂,如果超过时间了 线程也会自动解除挂起,参与锁的竞争工作
void unlock()
释放锁
public void unlock() {
sync.releaseShared(1); // 调用 AQS 的共享模式释放锁
}
releaseShared方法核心
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
//调用Sync实现的读锁释放方法
doReleaseShared();//如果释放锁成功后 调用doReleaseShared方法唤醒后续节点
return true;
}
return false;
}
doReleaseShared这里还是AQS方法了,做了幻想后续节点的操作
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
//这说明队列中至少有一个有效节点
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // CAS失败,重试
unparkSuccessor(h); // 唤醒后继节点
} else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // CAS失败,重试
}
if (h == head) // head未变化,退出循环
break;
}
}
步骤说明
①自旋进入判断队列有效节点只是有一个,然后进入
②通过唤醒状态判断 头节点后有需要唤醒的线程,通过CAS状态更新为0,如果成功则进行唤醒后续节点操作,如果不成功则进行自旋重试,确保只有一个线程触发唤醒操作,避免其他线程重复唤醒
③ unparkSuccessor(h); 唤醒后续节点并更新头节点
④ 如果步骤②中头节点状态非唤醒状态,则进行尝试通过 CAS 将状态从 0 改为 PROPAGATE,失败:说明其他线程修改了状态,继续循环
,成功则继续往下面走if判断头节点是否变化那去
这个方法到这里就要介绍下propagate状态了,在AQS中节点状态有 默认状态0,唤醒状态signal(-1),传播状态propagate(-3),另外还有取消状态1 和 条件状态-2,条件状态和条件队列有关,取消代表节点需要被清理,这里我们介绍前三种状态
唤醒状态signal,唤醒后续的节点,正常情况下同步队列中的状态是这个,当节点称为头节点后,要执行唤醒后续节点的操作,那么就会把节点状态从唤醒状态 -1 更新为0,如果头节点为0了,表示已经进行唤醒后续节点操作,在上面方法的case 1 里面体现了
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // CAS失败,重试
unparkSuccessor(h); // 唤醒后继节点
}
当 head 状态为 PROPAGATE 时,后续线程在获取资源时会 强制重试,确保释放信号被传播
传播状态propagate,上面方法还有个case 2 条件更新成传播节点,这个节点状态设计的巧妙
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
如果线程进入发现头节点状态为0,就把头节点状态设置为 传播状态PROPAGATE(-3),什么情况下进来会发现头节点会变成0呢,是特意为并发情况考虑的
如果线程1 和线程2同时执行释放锁的操作,这种情况下只能有一个进行CAS操作成功唤醒后续节点,那另外的那个线程怎么办,总不能不管了吧,处理并发的实现方式如下
线程1执行doReleaseShared方法--》头节点SIGNAL更新为0--》触发唤醒后续节点unparkSuccessor
线程2执行doReleaseShared方法--》头节点已为0 更新成PROPAGATE --》判断发现头节点未变化 结束方法
这个时候线程1的unparkSuccessor方法通过唤醒线程LockSupport.unpark(s.thread);又回到了类似doAcquireShared的请求获取锁的挂起处parkAndCheckInterrupt())那里,解除线程挂起,然后继续执行doAcquireShared方法的自旋(这个线程从哪里挂起的从哪里恢复执行)
for (;;) {
final Node p = node.predecessor();
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())//unparkSuccessor恢复到这里开始执行
interrupted = true;
}
然后再次进行自旋,又回到上面的请求获取锁,然后尝试获取锁呀,然后进入setHeadAndPropagate方法,这个方法除了设置新的头节点外,还做了是否触发doReleaseShared的操作
//参数propagate 是尝试获取锁tryAcquireShared(arg)的返回值,>0代表还有剩余资源可用
private void setHeadAndPropagate(Node node, int propagate) {
// 旧的头节点
Node h = head;
// 将当前获取到锁的节点设置为头节点
setHead(node);
// 如果仍然有多的锁(propagate的值是nonfairTryAcquireShared()返回值)
// 或者旧的头结点为空,或者头结点的 ws 小于0
// 又或者新的头结点为空,或者新头结点的 ws 小于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();
}
}
其中一个条件h.waitStatus < 0 ,旧的头节点状态是否小于0,上面我们说线程2把头节点设置成 传播状态-3,是满足小于0的,又触发了doReleaseShared()方法,是不是感觉又回来了
线程2再次进入doReleaseShared()方法,这个时候的头节点已经是线程1执行后更新头节点,这个时候根据新的头节点再次判断是否满足触发后续节点唤醒操作,如果触发唤醒解挂线程回到请求挂起处
传播状态propagate总结
1.生成背景:多线程并发下竞争头节点做唤醒后续节点操作失败的线程,更新头节点状态成传播状态
2.作用:在请求获取锁更新头节点方法,通过旧头节点状态判断 触发doReleaseShared,这里作用一加速传播:加速唤醒后继节点,在更新头节点时候就可以触发一次释放唤醒后续节点的操作方法doReleaseShared,毕竟,调用 doReleaseShared 方法越多、越早就越有可能更快的唤醒后继节点,作用二:强制传播,判断条条件里面 propagate > 0 || h.waitStatus < 0 或者关系,propagate 是尝试获取锁资源的返回值,说明即使没有资源了(propagate < =0),旧节点为传播状态这样也会触发释放方法doReleaseShared
细节注意哈,doReleaseShared方法只是唤醒后续节点,并不代表它就是执行成功了,从恢复解挂的线程unparkSuccessor回到doAcquireShared处,解挂还要自旋的 还在再判断是否有可用资源的,通过if (tryAcquireShared(arg) >= 0),不通过的还是再次被挂起的,propagate状态不是说一定能拿到锁的,只是给拿到锁这一过程 提提速
Condition newCondition()
条件实例,这个没有重写只有引用Lock接口
public Condition newCondition() {
throw new UnsupportedOperationException();
}
总结
方法作用总结
lock方法 :优先考虑的是拿到锁,不可中断,方法内设置 记录中断标识 给上一层代码去处理,直到拿到锁结束
lockInterruptibly方法:优先考虑的是响应中断,获取锁的过程中如果有中断,即中断当前线程获取锁的过程,清除加入队列的节点,原先线程执行的继续执行
tryLock方法:快速尝试获取锁,会立即给出一个拿到锁没有拿到锁的 结果值
tryLock(long time, TimeUnit unit)方法:规定时间内快速尝试获取锁,并且可以响应中断,如果有中断或者规定时间内没有拿到锁,立即返回false,因时间单位很小,也属于立即给出是否拿到锁的结果值
unLock:释放锁 并唤醒后续节点,解挂后续节点线程,和Lock系列方法闭环了
Condition:将已经获取锁的线程(获取锁的线程就已经不在同步队列了),通过设定的执行条件是否满足决定是否暂停该线程的执行,通过await方法将其转移如 条件队列,直到被精准唤醒该线程,然后在将该线程从条件队列清除,在封装成节点从队尾加入同步队列再次竞争锁
共享模式和独占模式中头节点状态变化
独占模式 :刚称为头节点状态是 唤醒状态SIGNAL = -1,当头节点执行了唤醒后续节点操作 就更新为0
共享模式:刚称为头节点状态是 唤醒状态SIGNAL = -1当头节点执行了唤醒后续节点操作 就更新为0
但是共享模式是同一时间多个线程持有锁,所以有多线程并发执行释放锁的情况,这个时候操作的头节点是同一个,第一个执行更新头节点的变成0成功,第二个线程没竞争过就会将头节点状态再从默认状态0 更新为 传播状态propagate = -3,上面详细描述该状态作用
主要是独占模式 会设置独占线程 同一时间只有一个线程可以运行操作,不存在并发情况
而共享模式是存在并发执行的情况,所以单独出现了传播状态propagate 来处理这类情况