一、Lock简介
Lock是并发访问共享资源的另一种方式,当需要使用chain lock这种时会很灵活,jdk文档中举了一个例子:(For example, some algorithms for traversing concurrently accessed data structures require the use of "hand-over-hand" or "chain locking": you acquire the lock of node A, then node B, then release A and acquire C, then release B and acquire D and so on.)
而Lock通常可以结合Condition来使用(通过lock.newCondition()方法获得),当使用Lock来替代Synchronized同步方法或者同步语句块时,Condition替代对象监视器方法(wait(),notify(),notifyAll())
二、AQS简介
AQS是一个抽象类,使用了模板设计模式,定义了一些抽象方法供子类重写,在AQS一些方法中会调用这些被子类重写的方法。
在文章初识Lock与AbstractQueuedSynchronizer(AQS)中对AQS进行了总结:
三、AQS详解
1、排队区分为同步队列和条件队列。同步队列只有一个,条件队列可以有多个,只有获取锁后才能进入条件队列
同步队列的结构是怎样的呢?
查看AQS源码,看到两个字段:
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
得知AQS的同步队列是由头结点和尾结点维护的,那么再看看Node里面的内容:
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** 线程取消 */
static final int CANCELLED = 1;
/** 后继节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程能够运行 */
static final int SIGNAL = -1;
/** 在同步队列等待 */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/** 节点状态(SIGNAL、CANCELLED、CONDITION、PROPAGATE、0)*/
volatile int waitStatus;
/** 前驱节点 */
volatile Node prev;
/** 后继结点 */
volatile Node next;
/** 同步队列的下一个结点,或者是特殊值SHARED。因为同步队列只有在独占模式下使用,通过这个字段可以表明是否是共享模式 */
Node nextWaiter;
...
}
可以看到Node拥有前驱和后继,所以同步队列是一个双向队列,在AQS中管理同步队列的数据结构大致如下(摘自深入理解AbstractQueuedSynchronizer(AQS)):
2、独占锁
(1)独占锁获取锁
使用Lock的lock实际上会调用acquire方法。
public final void acquire(int arg) {
/** 尝试以独占的方式获取锁,获取成功则返回,否则将线程包装成结点(独占模式)并放入同步队列 */
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
① tryAcquire方法
//Attempts to acquire in exclusive mode.(需要被子类覆盖)
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
② addWaiter方法
/**
* 创建入队结点
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
//采用尾插法
//如果尾结点不为null,直接插入队尾
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果尾结点为null,此处会进行初始化入队的操作
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))//设置头结点,将头尾指针指向该节点
tail = head;
} else {//重复执行插入队尾的操作
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
③ acquireQueued方法
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @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();//1.获得前继结点
if (p == head && tryAcquire(arg)) {//2.如果前继结点是头结点,并且成功获取同步状态,即可以获得独占式锁
setHead(node);//3.成功后设置头结点
p.next = null; // 释放前继结点
failed = false;
return interrupted;
}
//4.获取锁失败尝试将该结点挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//shouldParkAfterFailedAcquire返回true的话,通过parkAndCheckInterrupt阻塞线程
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);//如果失败,取消尝试获取
}
}
/**
* 获取锁失败的时候检查和更新状态.
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
*前继结点状态已经是signal了,当前继结点释放锁时会通知当前结点,所以可以安全地挂起了
*/
return true;
if (ws > 0) {
/*
* 前继结点可能由于超时或中断被取消了,需要重新找到状态不是被取消的结点
* 该步骤如图1所示
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 此时waitStatus只会是0或者PROPAGATE.表示需要signal状态但是暂时不挂起.
* 使用CAS设置前继结点状态为Signal,外层死循环会重新调用
* shouldParkAfterFailedAcquire(),直到compareAndSetWaitStatus设置成功,
* 此时前继结点状态为signal,返回true,可以安心挂起了呀
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
/**
* 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;//1.将结点里包装的线程置空,node不再关联到任何线程
// 2.向前找到一个有效的、没有被取消的前继结点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;//实际上就是node
node.waitStatus = Node.CANCELLED;//3.将node的waitStatus置为CANCELLED
//4.如果node是tail,更新tail为pred,并使pred.next指向null
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
//5. 如果node既不是tail,又不是head的后继节点(pred != head)
//则将node的前继节点的waitStatus置为SIGNAL
//并使node的前继节点指向node的后继节点(相当于将node从队列中删掉了)
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 {
//6. 如果node是head的后继节点,则直接唤醒node的后继节点
unparkSuccessor(node);
}
node.next = node; // help GC 后继指向自己
}
}
如果是步骤6的情况 :node是head的后继节点,后继指向自己后,队列如下图所示,此时node被取消了,但还在队列中
下面对cancelAcquire的解析内容摘抄修改自(Java AbstractQueuedSynchronizer源码阅读3-cancelAcquire()作者:lzwang2)
cancelAcquire()调用了unparkSuccessor()
不过,unparkSuccessor()中并没有对队列做任何调整呀。
这次,cancelAcquire()对于出队这件事情可以说是啥都没干。
出队操作实际上是由unparkSuccessor()唤醒的线程执行的。
unparkSuccessor()会唤醒successor关联的线程(暂称为sthread),当sthread被调度并恢复执行后,将会实际执行出队操作。
现在需要搞清楚sthread是从什么地方恢复执行的呢?这要看sthread是在哪里被挂起的。在哪里跌倒的,就在哪里站起来。
本文开头在使用场景中,列出了调用cancelAcquire()的所有接口,也正是在这些接口中,线程将有可能被挂起。这些方法的代码结构类似,主体是一个for循环。这里以acquireQueued()为例,如下所示:
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;
}
sthread当初就是被parkAndCheckInterrupt()给挂起的,恢复执行时,也从此处开始重新执行。sthread将会重新执行for循环,执行到shouldParkAfterFailedAcquire()处。shouldParkAfterFailedAcquire()中将会调整successor的prev指针(同时也调整head的next指针),从而完成了node的出队操作。
此时变为下图:
(2)独占锁释放锁
/**
* 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}.
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {//如果同步状态释放成功(tryRelease返回true)
Node h = head;
if (h != null && h.waitStatus != 0)//队列头结点不为空且状态不为0
unparkSuccessor(h);//唤醒 h 的后继结点
return true;
}
return false;
}
/** 尝试设置释放的状态 */
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* 1.如果node的状态小于0,,(i.e., possibly needing signal),尝试使用CAS设置状态为0
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* 2.唤醒node的后继结点。如果后继结点被取消或者是空,从队尾开始遍历
* 找到没有被取消的后继结点
*/
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)//如果存在没有被取消的结点
LockSupport.unpark(s.thread);//唤醒结点线程
}
每一次锁释放后就会唤醒队列中该节点的后继节点所引用的线程,验证了获得锁的过程是一个FIFO(先进先出)的过程。
(3)可中断式获取锁
调用lock.lockInterruptibly()实际上调用的是AQS的acquireInterruptibly
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))//获取失败
doAcquireInterruptibly(arg);
}
/**
* Acquires in exclusive interruptible mode.
* @param arg the acquire argument
*/
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逻辑基本一致,只是parkAndCheckInterrupt返回true时,在acquireQueued通过一个布尔类型的标志interrupted = true 来表示中断,而doAcquireInterruptibly中断时直接抛出异常。
(4)超时等待获取锁
调用lock.tryLock(timeout,TimeUnit)实际上调用了AQS的tryAcquireNanos
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;//1. 根据超时时间和当前时间计算出截止时间
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {//2.成功获取,出队
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();//3.1 重新计算超时时间
if (nanosTimeout <= 0L)//3.2 已经超时,直接返回false
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3、共享锁
(1)共享锁获取(该部分解析修改自深入浅出AQS之共享锁模式 作者:凌风郎少)
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
/**
* Acquires in shared uninterruptible mode.
* @param arg the acquire argument
*/
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) {//成功获取 等于0表示不用唤醒后继节点,大于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);
}
}
/**
* 设置队列头, 以及检查是否有共享模式的后继结点在等待, if so propagating if either
* propagate > 0 or PROPAGATE status was set.
*
* @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.propagate > 0 表示调用方指明了后继节点需要被唤醒
* 2.头节点后面的节点需要被唤醒(waitStatus<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();//详细查看下面释放锁的解析
}
}
(2)共享锁释放(该部分解析修改自深入浅出AQS之共享锁模式 作者:凌风郎少)
//释放共享模式的锁
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
/**
* Release action for shared mode -- signals successor and ensures
* propagation. (Note: For exclusive mode, release just amounts
* to calling unparkSuccessor of head if it needs signal.)
*/
private void doReleaseShared() {
for (;;) {
//唤醒操作由头结点开始,注意这里的头节点已经是上面新设置的头结点了
//其实就是唤醒上面新获取到共享锁的节点的后继节点
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
//这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
//如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//如果头结点没有发生变化,表示设置完成,退出循环
//如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试
if (h == head) // loop if head changed
break;
}
}
注:上面的setHeadAndPropagate()方法表示等待队列中的线程成功获取到共享锁,这时候它需要唤醒它后面的共享节点(如果有),但是当通过releaseShared()方法去释放一个共享锁的时候,接下来等待独占锁跟共享锁的线程都可以被唤醒进行尝试获取。
处于独占模式下时,其他线程试图获取该锁将无法取得成功。在共享模式下,多个线程获取某个锁可能(但不是一定)会获得成功,所以需要判断头结点是否发生了变化。
4、总结
头节点可以认为就是当前占有锁资源的节点
A、独占模式:
(1)不可中断:
I.获取锁
①线程先去尝试获取锁,如果获取失败后该线程被包装成节点Node,进入同步队列,如果同步队列尾结点不为空,将该节点设置为新的尾结点,否则进入同步队列的初始化。
②此时排队的线程仍未被挂起,如果这个节点是同步队列第一个节点,它会再次去尝试获取锁,获取失败的话会尝试将该节点挂起。判断如果该线程前继结点的状态为signal,则直接挂起,否则可能被挂起后前继结点不会唤醒它,所以用CAS设置前继结点状态为Signal(这期间线程不响应中断,只是设置一个中断的标志interrupted = true)
II.释放锁
释放锁的顺序是从队头H开始的,如果H等待状态小于0,将H的等待状态设置为0;如果H不为空,获取头结点的直接后继结点(H.NEXT),如果该结点(H.NEXT)为空,则从队列尾部遍历寻找离H最近,并且不是取消状态的结点。通过LockSupport.unpark(s.thread)唤醒线程
(2)可中断:
与不可中断类似,但在步骤2中尝试将线程挂起如果满足条件会响应中断并抛出异常
B、共享模式:
tryAcquireShared返回的不是boolean值,而是int。负数:表示获取失败;零值:表示当前结点获取成功, 但是后继结点不能再获取了;正数:表示当前结点获取成功, 并且后继结点同样可以获取成功
(1)获取锁
如果获取成功,则将自己设置为头结点,等待状态如果是signal,此时唤醒后继结点中同样是共享模式的结点,将自己状态设置为0。等待状态如果是0(没有排队线程),则将自己状态设置为PROPAGATE,告知后面的线程可以来直接获取锁,如果是独占模式,会直接离开队列。
(2)释放锁
释放成功会唤醒后继结点,如果自己状态为SIGNAL,则唤醒后继结点,如果不是,将自己状态设置为PROPAGATE