问题:
1.AQS中为什么要有一个虚拟的head节点?
AQS可以没有head,设计之初指定head只是为了更方便的操作。方便管理双向链表而已,一个哨兵节点
的存在。比如ReentrantLock中释放锁资源时,会考虑是否需要唤醒后继节点。如果头结点的状态不是-1。
就不需要去唤醒后继节点。唤醒后继节点时,需要找到head.next节点,如果head.next为null,或者
是取消了,此时需要遍历整个双向链表,从后往前遍历,找到离head最近的Node。规避了一些不必要的
唤醒操作。如果不用虚拟节点(哨兵节点),当前节点挂起,当前节点的状态设置为-1。可行。AQS本身
就是使用了哨兵节点做双向链表的一些操作。
网上说了,虚拟的head,可以避免重复唤醒操作。虚拟的head并没有处理这个问题。
2.AQS中为什么使用双向链表?
AQS的双向链表就为了更方便的操作Node节点。
在执行tryLock,lockInterruptibly方法时,如果在线程阻塞时,中断了线程,此时线程会执行
cancelAcquire取消当前节点,不在AQS的双向链表中排队。如果是单向链表,此时会导致取消节点,
无法直接将当前节点的prev节点的next指针,指向当前节点的next节点。
3.AQS中唤醒线程,为什么是从后往前找,而不是从前往后找?
因为防止跳过新加入的节点。
+------+ prev +-----+<---- +-----+
head | | <---- | | | | tail
+------+ ----> +-----+ ----> +-----+
新加入的节点,会先指向前一个节点,再tail指向新节点,最后再把前一个节点的next指向新的节点。
如果在前一个节点最后还没指向next节点时,此时发现next是null,就会跳过新加入节点的唤醒。
所以从后往前找。
目录
一、AQS
1、AQS概述
AQS就是AbstractQueuedSynchronizer抽象类,AQS其实就是JUC包下的一个基类,JUC下的很多内容都是基于AQS实现了部分功能,比如ReentrantLock,ThreadPoolExecutor,阻塞队列,CountDownLatch,Semaphore,CyclicBarrier等等都是基于AQS实现。
首先AQS中提供了一个由volatile修饰,并且采用CAS方式修改的int类型的state变量。
其次AQS中维护了一个双向链表,有head,有tail,并且每个节点都是Node对象
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;//后继线程需要唤醒
static final int CONDITION = -2;//等待condition唤醒
static final int PROPAGATE = -3;//共享式同步状态获取将会无条件地传播下去
volatile int waitStatus;//当前节点在队列中的状态,初始为0,状态是上面的几种
volatile Node prev;//前置节点
volatile Node next;//后继节点
volatile Thread thread;
}
JUC-->AQS-->AbstractQueuedSynchronizer:字面意思:抽象的队列同步器
AQS是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态
CLH:Craig、Landin and Hagersten队列,是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列FIFO。
@author Doug Lea
public class ReentrantLock implements Lock, java.io.Serializable {
...
abstract static class Sync extends AbstractQueuedSynchronizer {
...
abstract void lock();
...
}
@author Doug Lea
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
...
@author Doug Lea
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
...
abstract static class Sync extends AbstractQueuedSynchronizer {
...
@author Doug Lea
public class Semaphore implements java.io.Serializable {
...
abstract static class Sync extends AbstractQueuedSynchronizer {
并发大神 Doug Lea,提出统一规范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。
加锁会导致阻塞:有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理
解释说明:抢到资源的线程直接使用处理业务逻辑,抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),但等候线程仍然保留获取锁的可能且获取锁流程仍然继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。
既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?
如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的节点(Node),通过CAS,自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的控制效果。
2、AQS内部结构和属性
/**
* Provides a framework for implementing blocking locks and related
//为实现阻塞锁和相关的同步器提供一个框架,它是依赖于先进先出的一个等待
* synchronizers (semaphores, events, etc) that rely on
* first-in-first-out (FIFO) wait queues. This class is designed to
* be a useful basis for most kinds of synchronizers that rely on a
* single atomic {@code int} value to represent state. Subclasses
* must define the protected methods that change this state, and which
//依靠单个原子int值来表示状态,通过占用和释放方法,改变状态值
* define what that state means in terms of this object being acquired
* or released. Given these, the other methods in this class carry
* out all queuing and blocking mechanics. Subclasses can maintain
* other state fields, but only the atomically updated {@code int}
* value manipulated using methods {@link #getState}, {@link
* #setState} and {@link #compareAndSetState} is tracked with respect
* to synchronization.
......
* @since 1.5
* @author Doug Lea
*/
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
有阻塞就需要排队,实现排队必然需要队列
AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对state值的修改。
AQS=state变量+CLH变种的双端队列
* @since 1.5
* @author Doug Lea
*/
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
...
/**
* Wait queue node class.
*
* <p>The wait queue is a variant of a "CLH" (Craig, Landin, and
* Hagersten) lock queue. CLH locks are normally used for
* spinlocks. We instead use them for blocking synchronizers, but
* use the same basic tactic of holding some of the control
* information about a thread in the predecessor of its node. A
* "status" field in each node keeps track of whether a thread
* should block. A node is signalled when its predecessor
* releases. Each node of the queue otherwise serves as a
* specific-notification-style monitor holding a single waiting
* thread. The status field does NOT control whether threads are
* granted locks etc though. A thread may try to acquire if it is
* first in the queue. But being first does not guarantee success;
* it only gives the right to contend. So the currently released
* contender thread may need to rewait.
*
* <p>To enqueue into a CLH lock, you atomically splice it in as new
* tail. To dequeue, you just set the head field.
* <pre>
* +------+ prev +-----+ +-----+
* head | | <---- | | <---- | | tail
* +------+ +-----+ +-----+
* </pre>
...
//队列中每个排队的个体就是一个Node,Node<Thread>, Node=waitStatus+前后指针指向
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;//后继线程需要唤醒
static final int CONDITION = -2;//等待condition唤醒
static final int PROPAGATE = -3;//共享式同步状态获取将会无条件地传播下去
volatile int waitStatus;//当前节点在队列中的状态,初始为0,状态是上面的几种
volatile Node prev;//前置节点
volatile Node next;//后继节点
volatile Thread thread;//表示处于该节点的线程
...
}
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
....
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
AQS同步队列的基本结构
加锁流程
3、AQS源码分析
3.1、ReentrantLock类分析
从ReentrantLock开始解读AQS,Lock接口的实现类,基本都是通过[聚合]了一个[队列同步器]的子类完成线程访问控制的。
对比公平锁和非公平锁的tryAcquire()方法的实现代码,其实差别在于非公平锁获取锁时比公平锁少一个判断!hasQueuedPredecessors()
//查看是否有线程在AQS的双向队列中排队
//返回false,代表没人排队
public final boolean hasQueuedPredecessors() {
// 头尾节点
Node t = tail;
Node h = head;
// s为头结点的next节点
Node s;
// 如果头尾节点相等,证明没有线程排队,直接去抢占锁资源
return h != t &&
// s节点不为null,并且s节点的线程为当前线程(排在第一名的是不是我)
((s = h.next) == null || s.thread != Thread.currentThread());
}
!hasQueuedPredecessors()中判断了是否需要排队,导致公平锁和非公平锁的差异如下:
公平锁:公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中。
非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程在unpark(),之后还是需要竞争锁(存在线程竞争的情况下)
public class ReentrantLock implements Lock, java.io.Serializable {
....
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//公平锁
static final class FairSync extends Sync {
final void lock() {
// 执行acquire,尝试获取锁资源
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
}
//非公平锁
static final class NonfairSync extends Sync {
final void lock() {
// 上来就先基于CAS的方式,尝试将state从0改为1
if (compareAndSetState(0, 1))
// 获取锁资源成功,会将当前线程设置到exclusiveOwnerThread属性,代表是当前线程持有着锁资源
setExclusiveOwnerThread(Thread.currentThread());
else
// 执行acquire,尝试获取锁资源
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
3.2、AQS源码分析
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class AQSDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
// 引入银行办理业务场景
// 3个线程模拟3个来银行办理业务的客户,暂时只开一个受理窗口
// A 顾客是第一个顾客,受理窗口没有人,A可以直接去办理
new Thread(() -> {
lock.lock();
try {
System.out.println("--A thread come in");
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}, "A").start();
// B 顾客,第二个来的只能等待
new Thread(() -> {
lock.lock();
try {
System.out.println("--B thread come in");
} finally {
lock.unlock();
}
}, "B").start();
// C 顾客,第三个来的只能等待
new Thread(() -> {
lock.lock();
try {
System.out.println("--C thread come in");
} finally {
lock.unlock();
}
}, "C").start();
}
}
3.2.1、lock方法
1、执行lock方法后,公平锁和非公平锁的执行套路不一样
// 非公平锁
final void lock() {
// 上来就先基于CAS的方式,尝试将state从0改为1
if (compareAndSetState(0, 1))
// 获取锁资源成功,会将当前线程设置到exclusiveOwnerThread属性,代表是当前线程持有着锁资源
setExclusiveOwnerThread(Thread.currentThread());
else
// 执行acquire,尝试获取锁资源
acquire(1);
}
// 公平锁
final void lock() {
// 执行acquire,尝试获取锁资源
acquire(1);
}
2、acquire方法,是公平锁和非公平锁的逻辑一样
public final void acquire(int arg) {
// tryAcquire:再次查看,当前线程是否可以尝试获取锁资源
if (!tryAcquire(arg) &&
// 没有拿到锁资源
// addWaiter(Node.EXCLUSIVE):将当前线程封装为Node节点,插入到AQS的双向链表的结尾
// acquireQueued:查看我是否是第一个排队的节点,如果是可以再次尝试获取锁资源,如果长时间拿不到,挂起线程
// 如果不是第一个排队的节点,就尝试挂起线程即可
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 中断线程的操作
selfInterrupt();
}
3、tryAcquire方法竞争锁资源的逻辑,分为公平锁和非公平锁
// 非公平锁实现
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取了state属性
int c = getState();
// 判断state当前是否为0,之前持有锁的线程释放了锁资源
if (c == 0) {
// 再次抢一波锁资源
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
// 拿锁成功返回true
return true;
}
}
// 不是0,有线程持有着锁资源,如果是,证明是锁重入操作
else if (current == getExclusiveOwnerThread()) {
// 将state + 1
int nextc = c + acquires;
if (nextc < 0) // 说明对重入次数+1后,超过了int正数的取值范围
// 01111111 11111111 11111111 11111111
// 10000000 00000000 00000000 00000000
// 说明重入的次数超过界限了。
throw new Error("Maximum lock count exceeded");
// 正常的将计算结果,复制给state
setState(nextc);
// 锁重入成功
return true;
}
// 返回false
return false;
}
// 公平锁实现
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// ....
int c = getState();
if (c == 0) {
// 查看AQS中是否有排队的Node
// 没人排队抢一手 。有人排队,如果我是第一个,也抢一手
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的双向队列中排队
//返回false,代表没人排队
public final boolean hasQueuedPredecessors() {
// 头尾节点
Node t = tail;
Node h = head;
// s为头结点的next节点
Node s;
// 如果头尾节点相等,证明没有线程排队,直接去抢占锁资源
return h != t &&
// s节点不为null,并且s节点的线程为当前线程(排在第一名的是不是我)
((s = h.next) == null || s.thread != Thread.currentThread());
}
4、addWaite方法,将没有拿到锁资源的线程扔到AQS队列中去排队
addWaiter():双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位。真正的第一个有数据的节点,是从第二个节点开始的。
// 没有拿到锁资源,过来排队, mode:代表互斥锁
private Node addWaiter(Node mode) {
// 将当前线程封装为Node,
Node node = new Node(Thread.currentThread(), mode);
// 拿到尾结点
Node pred = tail;
// 如果尾结点不为null
if (pred != null) {
// 当前节点的prev指向尾结点
node.prev = pred;
// 以CAS的方式,将当前线程设置为tail节点
if (compareAndSetTail(pred, node)) {
// 将之前的尾结点的next指向当前节点
pred.next = node;
return node;
}
}
// 如果CAS失败,以死循环的方式,保证当前线程的Node一定可以放到AQS队列的末尾
enq(node);
return node;
}
//进入队列
private Node enq(final Node node) {
for (;;) {
// 拿到尾结点
Node t = tail;
// 如果尾结点为空,AQS中一个节点都没有,构建一个伪节点,作为head和tail
if (t == null) {
头节点为new Node()空节点,占位使用,可称傀儡节点/哨兵节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 比较熟悉了,以CAS的方式,在AQS中有节点后,插入到AQS队列的末尾
node.prev = t;//把当前节点的前指针指向尾指针
if (compareAndSetTail(t, node)) {//比较并交换把当前要入队的节点和尾节点进行交换
t.next = node;//上一个节点的下一个节点是当前节点
return t;
}
}
}
}
5、acquireQueued方法,判断当前线程是否还能再次尝试获取锁资源,如果不能再次获取锁资源,或者又没获取到,尝试将当前线程挂起
shouldParkAfterFailedAcquire:如果前驱节点的waitStatus是SIGNAL(SIGNAL = -1;//后继线程需要唤醒)状态,即shouldParkAfterFailedAcquire方法会返回true,程序会继续向下执行parkAndCheckInterrupt方法,用于将当前线程挂起。
// 当前没有拿到锁资源后,并且到AQS排队了之后触发的方法。 中断操作这里不用考虑
final boolean acquireQueued(final Node node, int arg) {
// 不考虑中断
// failed:获取锁资源是否失败(这里简单掌握落地,真正触发的,还是tryLock和lockInterruptibly)
boolean failed = true;
try {
boolean interrupted = false;
// 死循环…………
for (;;) {//自旋
// 拿到当前节点的前继节点
final Node p = node.predecessor();
// 前继节点是否是head,如果是head,再次执行tryAcquire尝试获取锁资源。
if (p == head && tryAcquire(arg)) {
// 获取锁资源成功
// 设置头结点为当前获取锁资源成功Node,并且取消thread信息
setHead(node);
p.next = null;// help GC
// 获取锁失败标识为false
failed = false;
return interrupted;
}
// 没拿到锁资源……
// shouldParkAfterFailedAcquire:基于上一个节点来判断当前节点是否能够挂起线程,如果可以返回true,
// 如果不能,就返回false,继续下次循环
if (shouldParkAfterFailedAcquire(p, node) &&
// 这里基于Unsafe类的park方法,将当前线程挂起
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
// 在lock方法中,基本不会执行。
cancelAcquire(node);
}
}
// 获取锁资源成功后,先执行setHead,重新设置头节点
private void setHead(Node node) {
// 当前节点作为头结点 伪
head = node;
// 头结点不需要线程信息
node.thread = null;
node.prev = null;
}
//返回前节点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
// 当前Node没有拿到锁资源,或者没有资格竞争锁资源,看一下能否挂起当前线程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// -1,SIGNAL状态:代表当前节点的后继节点,可以挂起线程,后续我会唤醒我的后继节点
// 1,CANCELLED状态:代表当前节点以及取消了
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 上一个节点为-1之后,当前节点才可以安心的挂起线程
return true;
if (ws > 0) {
// 如果当前节点的上一个节点是取消状态,我需要往前找到一个状态不为1的Node,作为他的next节点
// 找到状态不为1的节点后,设置一下next和prev
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 上一个节点的状态不是1或者-1,那就代表节点状态正常,将上一个节点的状态改为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
3.2.2、tryLock方法
// tryLock方法,无论公平锁还有非公平锁。都会走非公平锁抢占锁资源的操作
// 就是拿到state的值, 如果是0,直接CAS浅尝一下
// state 不是0,那就看下是不是锁重入操作
// 如果没抢到,或者不是锁重入操作,告辞,返回false
public boolean tryLock() {
// 非公平锁的竞争锁操作
return sync.nonfairTryAcquire(1);
}
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;
}
tryLock(time,unit)
// tryLock(time,unit)执行的方法
public final boolean tryAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {
// 线程的中断标记位,是不是从false,别改为了true,如果是,直接抛异常
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquire分为公平和非公平锁两种执行方式,如果拿锁成功, 直接告辞,
return tryAcquire(arg) ||
// 如果拿锁失败,在这要等待指定时间
doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
// 如果等待时间是0秒,直接告辞,拿锁失败
if (nanosTimeout <= 0L)
return false;
// 设置结束时间。
final long deadline = System.nanoTime() + nanosTimeout;
// 先扔到AQS队列
final Node node = addWaiter(Node.EXCLUSIVE);
// 拿锁失败,默认true
boolean failed = true;
try {
for (;;) {
// 如果在AQS中,当前node是head的next,直接抢锁
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;
// shouldParkAfterFailedAcquire:根据上一个节点来确定现在是否可以挂起线程
if (shouldParkAfterFailedAcquire(p, node) &&
// 避免剩余时间太少,如果剩余时间少就不用挂起线程
nanosTimeout > spinForTimeoutThreshold)
// 如果剩余时间足够,将线程挂起剩余时间
LockSupport.parkNanos(this, nanosTimeout);
// 如果线程醒了,查看是中断唤醒的,还是时间到了唤醒的。
if (Thread.interrupted())
// 是中断唤醒的!
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
取消节点分析
取消节点整体操作流程:
1、线程设置为null
2、往前找到有效节点作为当前节点的prev
3、将waitStatus设置为1,代表取消
4、脱离整个AQS队列
4.1、当前Node是tail
4.2、当前节点是head的后继节点
4.3、不是tail节点,也不是head的后继节点
// 取消在AQS中排队的Node
private void cancelAcquire(Node node) {
// 如果当前节点为null,直接忽略。
if (node == null)
return;
//1. 线程设置为null
node.thread = null;
//2. 往前跳过被取消的节点,找到一个有效节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
//3. 拿到了上一个节点之前的next
Node predNext = pred.next;
//4. 当前节点状态设置为1,代表节点取消
node.waitStatus = Node.CANCELLED;
// 脱离AQS队列的操作
// 当前Node是尾结点,将tail从当前节点替换为上一个节点
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// 到这,上面的操作CAS操作失败
int ws = pred.waitStatus;
// 不是head的后继节点
if (pred != head &&
// 拿到上一个节点的状态,只要上一个节点的状态不是取消状态,就改为-1
(ws == 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
}
}
3.2.3、lockInterruptibly方法
// 这个是lockInterruptibly和tryLock(time,unit)唯一的区别
// lockInterruptibly,拿不到锁资源,就死等,等到锁资源释放后,被唤醒,或者是被中断唤醒
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);
}
}
private final boolean parkAndCheckInterrupt() {
//线程挂起,程序不会继续向下执行
LockSupport.park(this);//被阻塞中,正在排队等待中
//根据park方法 API描述,程序在下述三种情况下会继续向下执行
//1.被unpark
//2.被中断(interrupt)
//3.其他不合逻辑的返回才会继续向下执行
//因上述三种情况程序执行至此,返回当前线程的中断状态,并清空中断状态
// 这个方法可以确认,当前挂起的线程,是被中断唤醒的,还是被正常唤醒的。
// 中断唤醒,返回true,如果是正常唤醒,返回false
return Thread.interrupted();
}
3.2.4、unlock方法
释放锁流程概述
线程A持有当前锁,重入了一次,state=2
线程B和线程C获取锁资源失败,在AQS中排队
1、线程A释放锁资源调用unlock方法,就是执行了tryRelease方法
2、首先,判断是不是线程A持有着锁资源,
如果不是就抛异常。
如果是线程A持有着锁资源,对state - 1。
-1成功后,会判断state是否为0。
如果不是,方法结束。
如果为0,证明当前锁资源释放干净。
3、查看头节点的状态是否不为0(判断是否为-1),如果为0,代表后面没挂起的线程。
如果不为0,后续链表中有挂起的线程,需要唤醒。
在唤醒线程时,需要先将当前的-1,改为0,找到有效节点唤醒。找到之后,唤醒线程即可。
public void unlock() {
// 释放锁资源不分为公平锁和非公平锁,都是一个sync对象
sync.release(1);
}
// 释放锁的核心流程
public final boolean release(int arg) {
// 1.核心释放锁资源的操作之一
if (tryRelease(arg)) {
// 3.如果锁已经释放掉了,走这个逻辑
Node h = head;
// h不为null,说明有排队的
// 如果h的状态不为0(为-1),说明后面有排队的Node,并且线程已经挂起了。
if (h != null && h.waitStatus != 0)
// 唤醒排队的线程
unparkSuccessor(h);
return true;
}
return false;
}
// ReentrantLock释放锁资源操作
protected final boolean tryRelease(int releases) {
// 2.拿到state - 1(并没有赋值给state)
int c = getState() - releases;
// 判断当前持有锁的线程是否是当前线程,如果不是,直接抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// free,代表当前锁资源是否释放干净了。
boolean free = false;
if (c == 0) {
// 如果state - 1后的值为0,代表释放干净了。
free = true;
// 将持有锁的线程置位null
setExclusiveOwnerThread(null);
}
// 将c设置给state
setState(c);
// 锁资源释放干净返回true,否则返回false
return free;
}
// 唤醒后面排队的Node
private void unparkSuccessor(Node node) {
// 拿到头节点状态
int ws = node.waitStatus;
if (ws < 0)
// 先基于CAS,将节点状态从-1,改为0
compareAndSetWaitStatus(node, ws, 0);
// 拿到头节点的后续节点。
Node s = node.next;
// 如果后续节点为null或者,后续节点的状态为1,代表节点取消了。
if (s == null || s.waitStatus > 0) {
s = null;
// 如果后续节点为null,或者后续节点状态为取消状态,从后往前找到一个有效节点
for (Node t = tail; t != null && t != node; t = t.prev)
// 从后往前找到状态小于等于0的节点
// 找到离head最新的有效节点,并赋值给s
if (t.waitStatus <= 0)
s = t;
}
// 只要找到了这个需要被唤醒的节点,执行unpark唤醒
if (s != null)
LockSupport.unpark(s.thread);//唤醒head下一个节点
}
unparkSuccessor():唤醒head下一个节点后,回到上面线程被挂起的地方(parkAndCheckInterrupt),此线程被唤醒继续去争抢办理窗口,(假设)此时会抢到,出队。
当前持有锁的线程,永远不会处于排队队列中,这是因为:
当一个线程调用lock()获取锁时,只有两种情况:
1、锁处于自由状态(state==0),且它不需要排队,通过cas立刻获取到了锁,这种情况下,显然它不会处于排队队列中;
2、锁处于非自由状态,线程加入到了排队队列的队头(不包括head)等待锁。将来某一时刻,锁被其他线程释放,并唤醒这个排队的线程,线程唤醒后,执行tryAcquire,获取到了锁,然后重新维护排队队列,将自己从队列移出(acquireQueued方法)
干我们这行,啥时候懈怠,就意味着长进的停止,长进的停止就意味着被淘汰,只能往前冲,直到凤凰涅槃的一天!