简介
本章学习内容部分来自《java并发编程的艺术》第5章
图片基本都是直接从《java并发编程的艺术》截取的。
AQS ,AbstractQueuedSynchronizer ,即队列同步器。使用一个int型数据state来表示同步状态。当state=0的时候表示没有被占用,大于0表示被占用。它是构建锁或者其他同步组件的基础框架(如 ReentrantLock、ReentrantReadWriteLock、Semaphore 等),J.U.C 并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。它是 J.U.C 并发包中的核心基础组件。
使用方式
同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的3个最重要的方法(getState()、setState(int newState)和compareAndSetState(int expect, int update))来进行操作,因为它们能够保证状态的改变是安全的。然后AQS会提供给我们一些在子类中可以重写的方法,有了这些方法我们可以实现重入锁,读写锁等等的逻辑。
主要内置方法
AQS 主要提供了如下方法:
#getState()
:返回同步状态的当前值。#setState(int newState)
:设置当前同步状态。#compareAndSetState(int expect, int update)
:使用 CAS 设置当前状态,该方法能够保证状态设置的原子性。- 【可重写】
#tryAcquire(int arg)
:独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态。 - 【可重写】
#tryRelease(int arg)
:独占式释放同步状态。 - 【可重写】
#tryAcquireShared(int arg)
:共享式获取同步状态,返回值大于等于 0 ,则表示获取成功;否则,获取失败。 - 【可重写】
#tryReleaseShared(int arg)
:共享式释放同步状态。 - 【可重写】
#isHeldExclusively()
:当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占。 acquire(int arg)
:独占式获取同步状态。如果当前线程获取同步状态成功,则由该方法返回;否则,将会进入同步队列等待。该方法将会调用可重写的#tryAcquire(int arg)
方法;#acquireInterruptibly(int arg)
:与#acquire(int arg)
相同,但是该方法响应中断。当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException 异常并返回。#tryAcquireNanos(int arg, long nanos)
:超时获取同步状态。如果当前线程在 nanos 时间内没有获取到同步状态,那么将会返回 false ,已经获取则返回 true 。#acquireShared(int arg)
:共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;#acquireSharedInterruptibly(int arg)
:共享式获取同步状态,响应中断。#tryAcquireSharedNanos(int arg, long nanosTimeout)
:共享式获取同步状态,增加超时限制。#release(int arg)
:独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒。#releaseShared(int arg)
:共享式释放同步状态。
从上面的方法看下来,基本上可以分成 3 类:
- 独占式获取与释放同步状态
- 共享式获取与释放同步状态
- 查询同步队列中的等待线程情况
队列同步器的实现
同步队列
当线程获取同步状态失败的时候,这个线程就会和等待状态信息一起,包装为一个node节点,然后放到同步队列中,同步队列是一个fifo双向队列。
上面是同步队列的结构,同步器保存了头和尾节点。进队列的时候放在尾部,为了保证多线程安全,同步器提供了CAS的方法来添加尾部。
同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点,该过程如图5-3所示。
由于 设置首节点的只有获取到同步状态的线程,这样的线程在独占式锁中只有一个,所以不用考虑线程安全问题。
Node节点
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
//定义了共享的node
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
//独占式
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
//因为超时或者中断,节点会被设置为取消状态,被取消的节点不会参与到竞争中
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
//后继节点的线程处于等待状态,而当前节点的线程如果释放了
// 同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
//节点在等待队列中,节点线程等待在Condition上,
// 当其他线程对Condition调用了signal()后,该节点将会从等待队列中转移到同步队列中,加入到同步状态的获取中
static final int CONDITION = -2;
//表示下一次共享式同步状态获取,将会无条件地传播下去
static final int PROPAGATE = -3;
/**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
*/
//同步状态,各种取值在上面注释都说的挺清楚的。
volatile int waitStatus;
/**
* 前驱,当节点被添加到同步队列的时候被设置
*/
volatile Node prev;
/**
* 后继
*/
volatile Node next;
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
* 当前的线程
*/
volatile Thread thread;
/**
* Link to next node waiting on condition, or the special
* value SHARED. Because condition queues are accessed only
* when holding in exclusive mode, we just need a simple
* linked queue to hold nodes while they are waiting on
* conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive,
* we save a field by using special value to indicate shared
* mode.
*/
/** 等待队列中的后续节点。如果当前节点是共享的,
* 那么字段将是一个 SHARED 常量,也就是说节点类型(独占和共享)
* 和等待队列中的后续节点共用同一个字段
*/
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
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(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;
}
}
- Node节点中包含5种状态:
- SIGNAL :代表此节点的后继节点正在或者将要被阻塞,需要这个节点通过类似signal的信号去唤醒
- CANCELLED:由于超时或者中断,该节点被取消了。这个状态永远不会变了
- CONDITION:该节点位于condition队列上,具体什么意思之后再解析
- PROPAGATE:一个被释放的节点,也应当唤醒后续的node,仅仅针对于头结点。之后详细解释一下这个todo
- 0:初始化状态
独占式同步状态的获取与释放
顶层api:
- acquire(int arg) :获取同步状态,不响应中断
- doAcquireNanos(int arg, long nanosTimeout):超时获取同步状态
- acquireInterruptibly(int arg) :响应中断的独占式同步状态获取
acquire(int arg)
public final void acquire(int arg) {
//首先调用tryAcquire方法尝试获取同步状态,获取成功就返回
if (!tryAcquire(arg) &&
//获取不成功,先调用addWaiter方法构造节点,放到队列尾部
//然后acquireQueued会自旋等待
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- tryAcquire方法是要交给子类实现的,它代表的逻辑是获取同步状态,返回结果代表是否获取成功
- addWaiter方法将当前线程包装为一个独占式节点,放到同步队列的尾部
- acquireQueued方法,里面是一个死循环,不断尝试获取同步状态。但时实际获取一次获取不到就会被阻塞了。
addWaiter()
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;
//如果队列不为null
if (pred != null) {
//将尾部设置为当前节点的前驱
node.prev = pred;
//CAS方法将当前节点设置为尾部,如果设置成功就返回了
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//上面的CAS操作可能会失败,或者tail等于null,在这里不断尝试。
enq(node);
return node;
}
- 如果tail不为null,那么就直接利用cas设置当前node为tail
- 入股tail为null,或者是cas不成功,那么就会到enq函数中循环设置。
enq(node)
private Node enq(final Node node) {
//死循环
for (;;) {
//取出tail
Node t = tail;
//原尾节点不存在,创建首尾节点都是new Node()
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- 如果tail为null,那么说明head为null。此时要先创建head,并将tail也指向它。然后就回到了CAS的逻辑了。
- 此时的head是否是一个真正的节点不重要,因为同步状态释放的时候,唤醒的是head的next节点。此时的head可以看做是一个占位符
acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//死循环
for (;;) {
//获取当前node的前驱
final Node p = node.predecessor();
//如果前驱是head,那么就尝试获取同步状态
if (p == head && tryAcquire(arg)) {
//获取成功,当前节点出队
setHead(node);
//清除原头结点额引用,帮组gc
p.next = null; // help GC
failed = false;
return interrupted;
}
//获取失败,线程等待
//这个方法如果p为signal的时候会返回true,其余返回false
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//获取同步状态异常,取消获取
if (failed)
cancelAcquire(node);
}
}
- finally的方法是会在return之前执行的。
- 返回值是一个当前线程是否已经被中断
- 在循环当中,首先判断自己的前驱是否是头结点。只有头结点的next才有资格去尝试获取同步状态。如果是,尝试获取,如果成功了将自己设置为头节点。如果没有成功,会调用shouldParkAfterFailedAcquire方法,这个方法中判断当前线程是否需要挂起。parkAndCheckInterrupt()方法是将当前线程挂起,然后唤醒之后,返回当前线程是否被中断。
shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前一个节点的等待状态
int ws = pred.waitStatus;
//如果是SIGNAL返回true
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) {
/*
* 前驱节点已经被取消了,跳过前驱节点,这里递归去除被cancell的节点,一直要删除到node
* 的前驱节点为SINGNAL节点
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 0 或者 Node.PROPAGATE
//将pred的装态设置为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
从名字都可以看出这个方法的作用判断当前线程是否该被 挂起。
什么时候会被挂起呢?
前一个节点的状态为SIGNAL的时候。如果不是,通过上面的代码可以看出,如果前驱节点没有删除到根节点,那么返回的时候,前驱节点的状态值已经是SIGNAL了。第二次循环到这个函数,也会返回true。
这个方法设计得很精妙。
parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
//阻塞当前线程,等待有其他线程调用unpark()才能返回
LockSupport.park(this);
//返回当前线程是否被中断
return Thread.interrupted();
}
挂起当前线程,返回是否被中断。
cancelAcquire(node)
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
//将节点的等待线程置为空
node.thread = null;
// Skip cancelled predecessors
//获取前驱,然后,跳过前驱
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
/**
* 虽然从表面上看,pred.next和node是一样的,,但是存在多线程的情况
*/
Node predNext = pred.next;
//设置当前node的状态
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
//如果过tail是node,那么用CAS将pred设置为tail
if (node == tail && compareAndSetTail(node, pred)) {
//设置pred的next为null
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非首节点,pred的等待状态为Signal,或者可以被cas成为signal
//pred的线程非空
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
//若 node 的 下一个节点 next 的等待状态非 Node.CANCELLED ,
// 则调用 #compareAndSetNext(...) 方法,CAS 设置 pred 的下一个节点为 next 。
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//如果pred为首节点,或者上面的其他情况,那么唤醒node的下一个节点等待。
//因为node的前驱为首节点,说明此时应当node获取同步状态,但是由于node被设置取消,所以没有
//节点会去唤醒node的后继了额,所以这个时候需要唤醒。
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
在acquireQueued方法中被调用,判断failed是否为true,但是在循环中只能看到当failed为false的时候才会退出循环,返回,所以这个方法调用的时候,应该是出现了某种异常,导致不能继续获取的时候,在队列中取消当前节点。方法的具体逻辑看注释。
tryAcquireNanos
通过调用同步器的tryAcquireNanos(int arg, long nanosTimeout)方法可以超时获取同步状态,即在指定的时间段内获取同步状态,如果获取到同步状态则返回true,否则,返回false。底层调用doAcquireNanos方法,该方法提供了传统Java同步操作(比如synchronized关键字)所不具备的特性。在以前的java中,如果线程在被synchronized阻塞时被中断了,那么这个线程的标志位会被修改,但是还是会继续等待。这种情况就是不相应中断。doAcquireNanos方法在响应中断的基础上还增加了超时的功能。
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
- 若线程已经被中断,抛出异常
- 其余和之前的一样了
doAcquireNanos(int arg, long nanosTimeout)
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();
//如果时间不够了直接返回false,而且此时的failed=true,所以这个节点是会被
//消灭的
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);
}
}
- 计算到期时间
- 每次尝试获取同步状态失败之后,判断时间超了没有,如果超了,返回false。
- 和不延时的函数相比,挂起的条件多了一个,就是剩余时间要大于阈值,默认为1000.
- 其他的和之前说的那个函数一样。
acquireInterruptibly
AQS 提供了acquire(int arg)
方法,以供独占式获取同步状态,但是该方法对中断不响应,对线程进行中断操作后,该线程会依然位于CLH同步队列中,等待着获取同步状态。为了响应中断,AQS 提供了 #acquireInterruptibly(int arg)
方法。该方法在等待获取同步状态时,如果当前线程被中断了,会立刻响应中断,并抛出 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 {
//独占模式将当前线程包装为node放入同步队列。
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中的代码是会在return之前执行的。
finally {
if (failed)
cancelAcquire(node);
}
}
- 与 acquire(int arg)方法的仅有的差别就是,如果出现了中断,不是将 是否中断返回出去,而是抛出异常。
release() 独占状态释放
public final boolean release(int arg) {
//如果释放成功,获取head,
if (tryRelease(arg)) {
Node h = head;
//h.waitStatus != 0
/**
* 这里这样判断的原因是:
* 为了防止多线程情况下的重复执行unparkSuccessor函数
*/
if (h != null && h.waitStatus != 0)
//唤醒h的下一个节点。
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
//设置当前装填为0
compareAndSetWaitStatus(node, ws, 0);
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)
//这里和指点的park就对应上了。
LockSupport.unpark(s.thread);
}
-
tryRelease(arg)为子类实现,释放成功返回true,否则返回false
-
在if中判断h.waitStatus != 0的原因是:
比如在读写锁中,线程A和B都获取了这个锁,然后他们几乎同时释放,然后执行到这里,假如A先执行unparkSuccessor,方法,它会使head的状态为0.然后B判断h.waitStatus != 0,就可以避免unparkSuccessor方法被重复执行。
共享式同步状态的获取与释放
共享式和独占式的区别在于:
前者允许同一时间多个线程获取锁
后置允许同一时间只能有一个线程获取锁。
共享式同步状态获取
public final void acquireShared(int arg) {
//如果大于0,说明共享锁获取成功,不用阻塞当前线程
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
- tryAcquireShared子类定义,返回值大于等于0说明获取成功
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();
//如果前驱是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);
}
}
大部分代码和独占式的是一样的。个别地方有差异:
-
获取同步状态成功之后,不但要通过setHeadAndPropagate(node, r);来设置新的头结点,还要唤醒后继节点。
这一点和独占式的区别很大,共享式通过这个方式,不断唤醒之后的节点来尝试获取同步状态,从而达到共享状态同时被多个线程获取。
setHeadAndPropagate
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//设置头结点
setHead(node);
//propagate > 0 说明同步状态还能被其他线程获取
//
if (propagate > 0 || h == null || h.waitStatus < 0 ||
//判断原来的或者新的首节点,等待状态为 Node.PROPAGATE 或者 Node.SIGNAL 时,可以继续向下唤醒。
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
//唤醒后续的共享式获取同步状态的节点。
//这个方法在共享式释放的代码中也会掉用
doReleaseShared();
}
}
共享式同步状态超时获取
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
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);
}
}
和之前的差不多
共享式获取响应中断
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
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);
}
}
共享式同步状态释放
public final boolean releaseShared(int arg) {
//尝试释放同步状态成功之后,调用doReleaseShared唤醒后续线程
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
如果调用tryReleaseShared(子类实现)方法能够成功释放,那么调用doReleaseShared方法,唤醒后继节点
doReleaseShared
private void doReleaseShared() {
for (;;) {
Node h = head;
//当前节点中还有其他的node
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
/// 不管是共享还是独占只有结点状态为SIGNAL才尝试唤醒后继结点
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//唤醒后继节点
unparkSuccessor(h);
}
//如果ws已经为cancell,那么说明这个head之后的节点,已经唤醒过了
// 如果状态为0则更新状态为PROPAGATE,更新失败则重试
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//如果过程中head被修改了就重试
if (h == head) // loop if head changed
break;
}
}
- 只有head为SIGNAL才会尝试唤醒后继节点,然后将循环head设置为0.以此来退出循环
- 如果head不为SIGNAL,而是为0,那么循环将其设置为PROPAGATE,然后循环就会退出。
总结
AQS是java实现锁的基础。AQS的原理其实并不难,主要掌握住中间一个同步队列的操作。如果我们想实现一个锁,那么首先需要做的就是实现Lock接口,然后在内部实现一个继承自AQS的子类作为自己锁的同步队列。在子类中重写几个获取状态值和释放状态值的方法。然后在lock方法和unlock方法分别调用同步队列中的对应方法来实现就可以了。
以下是一个自定义的同步组件,TwinsLock
要求任何时候只能有两个线程获取到。所以很明显就是共享锁。看一下代码就知道了:
package com.kejia;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class TwinsLock implements Lock {
private final Sync sync = new Sync(2);
//定义自己的队列同步器
private static final class Sync extends AbstractQueuedSynchronizer{
Sync(int count){
if(count<=0){
throw new IllegalArgumentException("count must large than 0");
}
//调用队列同步器提供的3个基本方法
setState(count);
}
@Override
protected int tryAcquireShared(int arg) {
for(;;){
int current =getState();
int newCount = current - arg;
if(newCount<0||compareAndSetState(current,newCount)){
return newCount;
}
}
}
@Override
protected boolean tryReleaseShared(int arg) {
for(;;){
int current = getState();
int newCount = current + arg;
if(compareAndSetState(current,newCount)){
return true;
}
}
}
}
public void lock() {
sync.tryAcquireShared(1);
}
public void lockInterruptibly() throws InterruptedException {
}
public boolean tryLock() {
return false;
}
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
public void unlock() {
sync.releaseShared(1);
}
public Condition newCondition() {
return null;
}
}