一、AQS是什么
1、同步器模板,基于模板模式开发
2、提供 独占式(Exclusive) 和 共享式(Share) 两种同步方式。 独占式:公平锁 -- 排队,先到先得 / 非公平锁 -- 直接抢 共享式:同时获取
二、AQS数据结构
1、AQS底层是一个CLH虚拟双向队列(FIFO,不存在队列实例,仅有节点之间的关联关系)。AQS通过将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配
2、AQS使用一个Volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对State值的修改
三、代码 + 图文 对部分源码及工作流程的解析
- 关于 state 状态字段
/**
* AQS中维护了一个名为state的字段,意为同步状态,是由Volatile修饰的,用于展示当前临界资源的获锁情况
*/
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
/**
* 如果当前状态值等于预期值,那么就使用给定的更新值原子性地设置同步状态。
* CAS
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
- 关于自定义同步器
/**
* 自定义同步器时需要重写下面几个AQS提供的模板方法:
*/
/**
* 该线程是否正在独占资源。只有用到condition才需要去实现它。
*/
isHeldExclusively();
/**
* 独占方式。尝试获取资源,成功则返回true,失败则返回false。
*/
tryAcquire(int)
/**
* 独占方式。尝试释放资源,成功则返回true,失败则返回false。
*/
tryRelease(int);
/**
* 共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
*/
tryAcquireShared(int);
/**
* 共享方式。尝试释放资源,成功则返回true,失败则返回false。
*/
tryReleaseShared(int);
/**
* 默认情况下,每个方法都抛出 UnsupportedOperationException。
* 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。
* AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用
*
* 为了方便看,AQS的源码贴在下方
*/
-
关于AQS原理及流程。(以下结合 ReentrantLock 展示)
1、同步器核心思想:
- 被请求的共享资源处于空闲状态,则当前请求资源的工作线程为有效工作线程,并锁定共享资源 - 被请求的贡献资源被占用,则需要完整的线程阻塞、唤醒机制。(例如AQS利用CLH队列来实现)
2、ReentrantLock中 公平锁、非公平锁 与 AQS 的关系 (加锁和释放锁)
(以上流程图对应源码在下面(以非公平锁为例,公平锁差不多,不同处会说明)。AQS与ReentrantLock直接关联在 tryAcquire() 处)
- 以下为非公平锁的申请过程,由 第一步 到 第八步
/**
* 以下已省略部分代码,部分原有注释更直观,便不翻译
*/
public class ReentrantLock implements Lock, java.io.Serializable {
/**
* Acquires the lock.
*
* <p>Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
* <p>If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
* <p>If the lock is held by another thread then the
* current thread becomes disabled for thread scheduling
* purposes and lies dormant until the lock has been acquired,
* at which time the lock hold count is set to one.
*
* 实现自 Lock 类中的 lock()方法
*/
public void lock() {
sync.lock(); //第一步
}
/**
* Synchronizer providing all implementation mechanics
*/
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
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();
if (c == 0) {
/**
* 注意这两个if。在state状态为0,也就是没有被占用的情况下
* 会尝试去更改state的值,成功则会被独占
*/
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
/**
* 这里是如果当前线程已经是拥有锁了,则 state 数量增加
*/
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
static final class NonfairSync extends Sync {
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() { // 第三步
/**
* 非公平锁比公平锁多了这一步 主要就是为了 卡位
*/
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) { // 第五步
/**
* 这里调用的就是第六步
*/
return nonfairTryAcquire(acquires);
}
}
/**
* 公平锁
*/
static final class FairSync extends Sync {
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*
* 对比一下非公平锁的 nonfairTryAcquire() 方法
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/**
* 公平锁比非公平锁在 tryAcquire() 中多了 !hasQueuedPredecessors()
* 这一步骤用来判断同步队列中是否存在有效节点(队列会在下方说明)
* 如果返回False,说明当前线程可以直接争取共享资源;
* 如果返回True,说明队列中存在有效节点,当前线程必须加入到同步队列中
* 这是AQS的逻辑(注意:和队列相关的,都属于AQS的逻辑,代码贴在AQS类里)
*/
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;
}
}
}
/**
* 为了方便看,所以把部分AQS的源码贴在这里
*/
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/**
* Sets the thread that currently owns exclusive access.
* A {@code null} argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* {@code volatile} field accesses.
* @param thread the owner thread
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
}
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
static final class Node {
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
}
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) { //第四步
/**
* 这里的 tryAcquire() 是在调用第五步 也就是获取锁的过程
* 如果获取锁失败,则会执行 AQS 的后续逻辑,也就与 ReentrentLock 无关了
*/
if (!tryAcquire(arg) &&
/**
* 这里的 addWaiter() 是第七步,acquireQueued() 是第八步
* 这两步实际上就是把当前线程塞到队列里 先记着这么个步骤 后面说
*/
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //第七、八步
selfInterrupt();
}
/**
* 这个方法是需要被子类具体实现的 所以调用的应该是子类的方法
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
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(node);
return node;
}
/**
* 这个方法在源码中的注释太多了就不贴了 可以自行看看
*/
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
/**
* 这一行代码比较有意思 但是会牵扯到队列的东西 后面合着队列再说
* 这里先说明一下 : AQS 的同步队列为双向链表,而且第一个结点为虚拟节点
*/
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
}
- 以下为非公平锁的解锁过程
/**
* 省略部分代码
*/
public class ReentrantLock implements Lock, java.io.Serializable {
/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
public void unlock() { //第一步
/**
* 这里实际上调用的是 AQS 的 release()
*/
sync.release(1);
}
private final Sync sync;
/**
* 这里贴出来只是为了说明 Sync 继承自 AQS
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
/**
* 省略部分代码
*
* 在 Sync 内部类中 对于锁的释放只有这一个方法
* 从这里可以看出 释放锁是不区分公平或非公平锁的
* 释放锁的代码相对太简单了…… 自己看吧 没啥好说的
*/
protected final boolean tryRelease(int releases) { //第三步
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
}
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) { //第二步
if (tryRelease(arg)) {
/**
* 如果能进来,说明锁的释放已经成功了,此时该锁没有被任何线程占用
* (后面就是 AQS 的逻辑了)
*/
Node h = head;
/**
* 头结点不为空并且头结点的waitStatus不是初始化节点情况,解除线程挂起状态
* 这里的 h != null 是为了避免 head 还没初始化的情况
* 因为第一个节点进来之后会被初始化为一个虚节点,没有数据入队就会是
* head == null
* 至于 h.waitStatus != 0, 当 h.waitStatus == 0 代表当前节点的线程是活跃
* 的,不需要去唤醒,否则是被阻塞了,需要唤醒(这个地方需要结合下面的队列的
* acquireQueued() 中获取锁的部分结合来看)
*/
if (h != null && h.waitStatus != 0)
/**
* 这里是对阻塞的线程做唤醒操作
*/
unparkSuccessor(h);
return true;
}
return false;
}
/**
* Wakes up node's successor, if one exists.
* @param node the node
*/
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;
/**
* 从尾部节点到队首找到队列第一个 waitStatus < 0 的节点。
* 这里为什么要从后往前找 不从前往后找? 这里要结合 下面的 addWaiter() 来说
* (这里代码只粘贴部分)
* node.prev = pred; 一
* if (compareAndSetTail(pred, node)) { 二
* pred.next = node; 三
* return node;
* }
* 如上代码:1、一、二虽然不是原子的,但是这一部分是在一个死循环中,也就是说
* compareAndSetTail() 方法是一定会执行成功的,哪怕这次没有成功
* 所以一、二也算是原子操作了。所以 node.prev 一定会是前驱节点的
* tail,如此从后往前找数据不会有问题
* 2、二、三也不是原子的,且在这个方法中是没有被弥补,如果从前往
* 后找,在执行了 compareAndSetTail() 后,如果 pred.next = node;
* 还没执行就开始获取到 pred 节点,此时的 pred.next 为null,就会造
* 成 pred 为尾结点的错觉(这里可以看Node中 next 和 prev 的注释)
* 3、(保留意见)在cancelAcquire() 方法中,next指针会断开,prev不
* 会,所以用next来查找是不安全的。但是为什么说保留意见,因为
* acquireQueued() 中 cancelAcquire() 好像不太可能会被执行。
* (acquireQueued()在下方)
*/
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
/**
* 唤醒
*/
LockSupport.unpark(s.thread);
}
}
- 以下为AQS队列的操作
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
/**
* 以当前的线程和锁的模式新建一个节点
*/
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;
/**
* 这里通过 compareAndSetTail() 来完成对尾结点的设置
* 这里是根据对象属性相对于对象的偏移量来做的改变
*/
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
/**
* 1、如果Pred指针是Null(说明同步队列中没有元素),
* 2、或者当前Pred指针和Tail指向的位置不同(说明被别的线程已经修改)
* 就会走到这里
*
* 这个方法是和下面的 hasQueuedPredecessors() 方法有关系的 慢慢来
*/
enq(node);
return node;
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
/**
* 如果没有被初始化,需要进行初始化一个头结点出来
* 但是初始化的头结点不是当前线程节点,是调用了无参构造函数的节点
* addWaiter就是一个在双端链表添加尾节点的操作
* 但是要注意,双端链表的头结点是一个无参构造函数的头结点。
*/
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
/**
* hasQueuedPredecessors用于判断公平锁加锁时同步队列中是否存在有效节点
* 返回False,则当前线程可以争取共享资源;
* 返回True,则队列中存在有效节点,当前线程必须加入到同步队列中。
*/
public final boolean hasQueuedPredecessors() {
/**
* 注意这里源码给的注释 很细节也很重要! 下面会结合判断一起说明
*
*
* The correctness of this depends on head being initialized
* before tail and on head.next being accurate if the current
* thread is first in queue.
*/
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
/**
* 上面已经说过 AQS 的同步队列首节点为虚拟节点,不存储任何信息,只用来站位
* enq() 方法里面也可以看得出来。真正有数据的是从第二个节点开始的。所以:
* 当 h == t 时:
* 1、当h和t都为null,返回false。此时说明队列为空
* 2、当h和t都指向同一个Node,也返回false。说明此时队列中只有一个dummy
* node,也就是没有线程在队列中
*
* 当 h != t 时:
* 1、如果(s = h.next) == null,同步队列正在有线程进行初始化,但只是进行到
* ail指向Head,还未将Head指向Tail,此时队列中有元素,需要返回True
* 2、如果(s = h.next) != null,则此时队列中至少有一个有效节点。
* 如果此时 s.thread == Thread.currentThread(),则同步队列的第一个有
* 效节点中的线程与当前线程相同,那么当前线程可以获取资源
* 如果s.thread != Thread.currentThread(),说明同步队列的第一个有效节点
* 线程与当前线程不同,当前线程须加入同步队列
*
* (这里需要结合上面的 enq() 一起看。直接贴过来了)
* if (t == null) { // Must initialize
* if (compareAndSetHead(new Node()))
* tail = head; //下面说的会出现短暂的head != tail 就是这里
* // 因为 CAS 和 tail = head 这两步不是原子性的
* } else {
* node.prev = t;
* if (compareAndSetTail(t, node)) {
* t.next = node;
* return t;
* }
* }
*
* ☆☆☆ 节点入队不是原子操作,所以会出现短暂的head != tail,此时Tail指向最
* 后一个节点,而且Tail指向Head。如果Head没有指向Tail,这种情况下也需要将相
* 关线程加入队列中。所以这后半块判断代码是为了解决极端情况下的并发问题。
*/
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
/**
* 这里说上面又细节又重要的 head 和tail 的赋值
* 为什么上面要先 获取tail的数据 再获取head的数据?
* 也就是 Node t = tail; Node h = head; 这个顺序的原因
* 这里并不是随意写的,这里和判断中的 (s = h.next) == null 与 enq()
* 中(上面)的 compareAndSetHead(new Node()) 代码有关
* 很显然源码中这几处是没有控制执行顺序的,所以可能会出现以下几种情况
* (compareAndSetHead(new Node()) 太长, 以 AAA 代替)
* Node t = tail; Node t = tail; Node t = tail;
* Node h = head; AAA Node h = head;
* AAA Node h = head; AAA
* 此顺序 t 和 h 都为null 此顺序 h不为null,t为null 此顺序h和t都不为null
*
* 从上面三种情况可以看出 在通过 h != t 的情况下,h一定不会为null,然后再执行
* (s = h.next) == null 这条语句,则不会报空指针
* 如果这两行代码顺序替换 那就可能出现空指针异常
* 这里不存在指令重排序 因为这两个都是写指令 如果不清楚就去看 内存访问重排序
*/
}
}
- 以下是线程出队列
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
/**
* 没错 又是这块代码
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
/**
* 上面说了在 addWaiter() 中是线程入队列, 且该方法返回了一个Node
* 那么 acquireQueued() 则是拿到这个Node对象去获取锁,自然也就是出队列了
*/
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
/**
* 上面两者都为true,则进入到这里用来给线程标记中断状态
* 因为在 acquireQueued() 中的 parkAndCheckInterrupt() 会清除掉线程
* 的中断状态。何时响应中断,下面说
*/
selfInterrupt();
}
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*
* 下方有流程图
*/
final boolean acquireQueued(final Node node, int arg) {
/**
* 标记是否成功拿到资源
*/
boolean failed = true;
try {
/**
* 标记等待过程中是否中断过 这个字段对于当前没啥用,在别的地方会用到
* 别的地方就不说了 和这里关系不大了已经
*/
boolean interrupted = false;
/**
* 开始自旋, 这里要么是获取到锁资源并返回,要么就是被中断
*/
for (;;) {
/**
* 获取当前节点的前驱节点
*/
final Node p = node.predecessor();
/**
* 如果p是头结点,说明当前节点在真实数据队列的首部,
* 就尝试获取锁(因为头结点是虚节点)
*/
if (p == head && tryAcquire(arg)) {
/**
* 获取锁成功,头指针移动到当前node
*/
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
/**
* 如果p为头节点且当前没有获取到锁(可能是非公平锁被抢占了)
* 或者是p不为头结点,则需要判断当前node是否需要被阻塞
*(被阻塞条件:前驱节点的waitStatus为-1),防止无限循环浪费资源
* 请注意这两个方法 接下来慢慢说
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
/**
* 这里是和 取消状态 相关的逻辑
*/
cancelAcquire(node);
}
}
/**
* Sets head of queue to be node, thus dequeuing. Called only by
* acquire methods. Also nulls out unused fields for sake of GC
* and to suppress unnecessary signals and traversals.
* @param node the node
*
* 这里就是将指定节点设置为虚节点
*/
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*
* 靠前驱节点判断当前线程是否应该被阻塞
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
/**
* 获取前驱结点的节点状态 以下几个状态值 都在 AQS 内部类 Node中
*/
int ws = pred.waitStatus;
/**
* 前驱结点处于唤醒的状态
*/
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* ws > 0 代表取消 即: static final int CANCELLED = 1;
*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
/**
* 循环向前查找取消状态的节点,并从队列中剔除
*/
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*
* 设置前驱节点等待状态为SIGNAL
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
/**
* Convenience method to park and then check if interrupted
* @return {@code true} if interrupted
*
* 用于挂起当前线程,阻塞调用栈,返回当前线程的中断状态
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
/**
* Cancels an ongoing attempt to acquire.
*
* @param node the node
*/
private void cancelAcquire(Node node) {
/**
* Ignore if node doesn't exist
*/
if (node == null)
return;
/**
* 设置该节点不关联任何线程,也就是虚节点
*/
node.thread = null;
Node pred = node.prev;
/**
* 通过前驱节点,跳过取消状态的node
*/
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.
* 把当前node的状态设置为CANCELLED
*/
node.waitStatus = Node.CANCELLED;
/**
* 如果当前节点是尾节点,将从后往前的第一个非取消状态的节点设置为尾节点
* 如果更新成功,将tail的后继节点设置为null
*/
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.
* 如果当前节点不是head的后继节点:
* 1:判断当前节点前驱节点是否为SIGNAL,
* 2:如果不是,则把前驱节点设置为SINGAL看是否成功
* 如果1和2中有一个为true,再判断当前节点的线程是否为null
* 如果上述条件都满足,把当前节点的前驱节点的后继指针指向当前节点的后继节点
*/
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 {
/**
* 如果当前节点是head的后继节点,或者上述条件不满足,
* 那就唤醒当前节点的后继节点
*/
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
}
- 以下为 acquireQueued() 的流程
- 以下为 cancelAcquire() 的流程
从上面的 cancelAcquire() 流程图可以看出,变动的都是next指针,prev指针好像没动过?实际上 prev 指针的处理是在 shouldParkAfterFailedAcquire() 中进行的,也就是 node.prev = pred = pred.prev; 这行代码。如果又在 cancelAcquire() 中对 prev 进行操作,则可能会使得 prev 指向一个已经移除的 node 造成数据不安全。又因为能进入 shouldParkAfterFailedAcquire() 说明共享资源已经被获取,所以当前节点之前的节点都不会有变化,这个时候去改动 prev 指针会更安全
- 简单提一下上述源码中的 selfInterrupt()
/**
* Convenience method to interrupt current thread.
*
* 这里涉及到Java协作式中断
* Thread 类中提供了4个关于中断的方法:如下
* 1、public static boolean interrupted()
* 实际调用的是下面第四个方法,参数为true,代表清除状态
* 判断当前线程是否已经中断,并且会清除掉线程的中断状态
* 也就是说如果一个线程为中断状态,连续调用两次这个方法,第一次为true,第二次
* 会是false
* 2、public boolean isInterrupted()
* 实际调用的是下面第四个方法,参数为false,代表不清除状态
* 判断线程是否已经中断
* 3、public void interrupt()
* 是唯一能将中断状态设置为 true 的方法
* 4、private native boolean isInterrupted(boolean ClearInterrupted)
* 判断线程中断与否,并根据传参决定是否清除中断状态
*
* 如何理解协作式:调用 interrupt() 方法后给线程标记上中断,但是并不会立即中断,而是需要
* 在合适的地方来响应中断。而所谓的合适的地方,实际上取决于代码中是否有对
* 中断状态做判断并给予对应的中断操作,如果说没有调用对应的中断操作的代
* 码,则线程的中断并不会被响应(简单来说,我给你标记中断了,至于什么时候
* 处理这个中断,要不要处理这个中断,你自己决定)。
*
* AQS中,线程在被唤醒后就一直去获取锁了,此过程中并不会响应中断这个操作。
* 何时响应,要么自主实现中断响应操作,要么调用已有方法来响应中断,比如说
* ThreadPoolExecutor 中的 Worker 在调用 run() 时会调用 runWork(),其中会响应
*/
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
- 关于条件队列
推荐直接看这个 segmentfault.com/a/119000001… 。这篇博客中提到的bug在JDK10中被修复了