前置知识
- 公平锁和非公平锁
- 可重入锁
- 自旋思想
- LockSupport
- 数据结构之双向链表
- 设计模式之模板设计模式
AQS入门级别理论知识
是什么
AbstractQueuedSynchronizer:抽象的队列同步器。
用来实现锁或其他同步器组件的公共基础部分的抽象实现,是重量级基础框架及整个JUC体系的基石,主要用于解决锁分给谁的问题。
为了实现阻塞锁和相关的同步器提供的一个框架,依赖于一个先进先出的等待队列。依靠单个原子int值表示状态,通过占用和释放方法改变状态值。
AQS为什么是JUC内容中最重要的基石
和AQS有关的
ReentrantLock
CountDownLatch
ReentrantReadWriteLock
Semaphore
进一步理解锁和同步器的关系
锁:面向锁的使用者,定义了程序员和锁交互的使用层API,隐藏了实现细节,只需要调用即可。
同步器:面向锁的实现者,DougLee提出了统一规范并简化锁的实现,将其抽象出来屏蔽了同步状态管理、同步队列的管理和维护、阻塞线程排队和通知、唤醒机制等,是一切锁和同步组件实现的公共基础部分。
能干嘛
抢到资源的线程直接使用处理业务,抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),但等候线程仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。
加锁会导致阻塞,进而需要排队,就需要有队列。
如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS同步队列的抽象表现。它将要请求共享资源的线程及自身的等待状态封装成队列的结点对象(Node),通过CAS、自旋以及LockSupport.park()
的方式,维护state变量的状态,使并发达到同步的效果。
AQS使用一个volatile的int类型的成员变量state
表示同步状态,通过内置FIFO队列完成资源获取的排队工作,将每条要去抢占资源的线程封装成一个Node结点来实现锁的分配,通过CAS完成对State值的修改。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
static final class Node {
volatile int waitStatus;
volatile java.util.concurrent.locks.AbstractQueuedSynchronizer.Node prev;
volatile java.util.concurrent.locks.AbstractQueuedSynchronizer.Node next;
}
private transient volatile java.util.concurrent.locks.AbstractQueuedSynchronizer.Node head;
private transient volatile java.util.concurrent.locks.AbstractQueuedSynchronizer.Node tail;
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
}
小总结
AQS源码分析前置知识储备
AQS自身
AQS的int变量
AQS的同步状态state成员变量
/**
* The synchronization state.
* state=0:可以操作
* state>0:有线程占用
*/
private volatile int state;
AQS的CLH队列
CLH队列,是一个双向队列。尾部入队,头部出队。
小总结
阻塞→排队→队列=state变量+CLH双端队列。
内部类Node(Node类是AQS的内部类)
Node的int变量
/** 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 */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
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
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
表示等待区其他线程的等待昨天,队列中每个排队的个体就是一个Node。
Node此类的讲解
AQS源码深度讲解和分析
Lock接口的实现类,基本都是通过聚合了一个队列同步器的子类完成线程访问控制的。
ReentrantLock的原理
Lock.java
是一个接口,在创建Lock对象的时候,这里拿Lock的实现类ReentrantLock.java
为例。
ReentrantLock创建对象的时候,可以传true/false,也可以不传(等价于false),从而创建出来公平锁和非公平锁。
从最简单的lock()
方法开始看看公平和非公平
在执行lock()的时候,公平锁和非公平锁执行逻辑会不一样,顺着lock()
方法点进去。
lock()
→sync.lock()
→acquire()
公平锁:
final void lock() {
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;
}
非公平锁:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
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;
}
观察公平锁和非公平锁的tryAcquire()
方法,公平锁多了一个语句:!hasQueuedPredecessors()
,用来判断当前队列是否有前驱。
公平锁:先来先到,线程在获取锁的时候,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列。
非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象,也就是队列的第一个排队线程苏醒后,不一定是排头的这个线程获取的锁,它还是需要参加竞争锁(如果线程竞争的情况),后来的线程就插队夺锁了。
以非公平锁ReentrantLock
为例作为突破,了解lock()
方法
以非公平锁作为案例突破,解读源码。
import java.util.concurrent.locks.ReentrantLock;
public class AQSDemo {
public static void main(String[] args) {
// A、B、C去银行办理业务,只有一个窗口
ReentrantLock reentrantLock = new ReentrantLock();// 非公平锁
// A先到,此时窗口空闲,A获得优先办理的机会,办理业务
new Thread(() -> {
try {
reentrantLock.lock();
System.out.println("A is running");
try {
// A是一个耗时的任务,需要长期占用窗口
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
reentrantLock.unlock();
}
}, "A").start();
// B到了,此时A正在办理业务,只能等待窗口被释放,于是进入AQS等待队列,尝试去抢占窗口
new Thread(() -> {
try {
reentrantLock.lock();
System.out.println("B is running");
} finally {
reentrantLock.unlock();
}
}, "B").start();
// C到了,此时A正在办理业务,只能等待窗口被释放,于是进入AQS等待队列,尝试去抢占窗口,前面是B顾客
new Thread(() -> {
try {
reentrantLock.lock();
System.out.println("C is running");
} finally {
reentrantLock.unlock();
}
}, "C").start();
}
}
lock()
lock()
方法是Lock.java
接口里的,查看实现类,找到ReentrantLock.java
的lock()
方法,再看实现类,有FairSync
和NonFairSync
两个实现类。
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))// 第一个线程抢占
setExclusiveOwnerThread(Thread.currentThread());// 设置排他锁拥有者为当前线程
else// 第二个线程及后续线程抢占
acquire(1);
}
}
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
}
acquire()
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire(arg)
在父类里只提供一个模板,具体实现交由子类来实现。
AbstractQueuedSynchronizer.java
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
FairSync.java
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// hasQueuedPredecessors()用来判断队列前面是否有元素
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;
}
NonFairSync.java
会调用nonfairTryAcquire()
方法。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {// 当前可以抢占
// 尝试CAS
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;
}
对于tryAcquire()
方法,如果抢锁成功了,继续执行下面的流程,如果没有成功,就会进入队列进行排队,也就来到了addWaiter()
和acquireQueued()
方法。
addWaiter(Node.EXCLUSIVE)
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;// 初始的时候,tail和head都指向null
if (pred != null) {// 这一段if和enq()里的else的含义是一样的:双向链表的尾插法
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 加入到排队的等待队列
enq(node);// 第一次进这个方法的时候,会直接走enq()方法
return node;
}
private Node enq(final Node node) {
// 这里可以理解成一个双向链表的插入操作
for (;;) {
Node t = tail;
// 第一次进来
if (t == null) { // Must initialize
// 创建一个结点,并设置为头结点
if (compareAndSetHead(new Node()))
tail = head;// 让尾结点也指向这个结点,此时头结点=尾结点,这个结点叫虚拟结点/哨兵结点,用于占位,注意这里的node是new出来的,不是传进来的
} else {
node.prev = t;// 传进来的结点的prev指向t
if (compareAndSetTail(t, node)) {// 尝试将node标记为尾结点
t.next = node;// 建立next指向
return t;
}
}
}
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
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;
}
// 第一次执行shouldParkAfterFailedAcquire()结果为false,第二次执行shouldParkAfterFailedAcquire()结果为true,此时会继续走parkAndCheckInterrupt()
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 如果排队失败或者不想排队了,执行取消操作
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取前置结点的waitStatus
int ws = pred.waitStatus;
// 如果是SIGNAL,等待被占用资源释放直接返回true,准备调用parkAndCheckInterrupt()方法
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
// waitStatus > 0表示是CANCELED状态
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
// 循环判断当前结点的前置结点是否也为CANCELED,忽略该状态结点,重新连接队列
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.
*/
// 将前置结点的waitStatus标记为-1,这里意味着有一个结点已经阻塞了,前置结点有唤醒下一个结点的责任
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
// 线程挂起,程序不会继续向下执行
LockSupport.park(this);
// 根据park方法API描述,程序在这三种情况下,会继续向下执行
// 1.被unpark
// 2.被中断(interrupt)
// 3.其他不合逻辑的返回才会继续向下执行
// 上述3种情况,会执行到这里,返回当前线程中断状态并清空中断状态
// 由于被中断了,该方法返回true
return Thread.interrupted();
}
unlock()
sync.release(1)
→tryRelease(arg)
→unparkSuccessor()
在分析unlock()
的时候,还要回看前面的parkAndCheckInterrupt()
方法。
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;// 重新计算state的值,可重入锁,计算可重入次数
// 当前线程和持有锁的线程不是一个,一般不会出现这个情况
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);// 把排他锁持有者设置为null
}
setState(c);// 更新state的值
return free;
}
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.
*/
// 第一次进来,s为等待队列的第一个结点,且非null
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)
// 执行unpack()操作,唤醒s线程
LockSupport.unpark(s.thread);
}
看一下异常情况,也就是执行cancelAcquire()
方法的情况。
这里要结合双向链表的删除,考虑删除最后一个结点的情况和删除中间结点的情况。
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;
// CANCELED为1,大于0,那么继续向前找,直到碰到一个不是CANCELED的结点,一边找一边更新当前结点的前驱
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.
// Before, we are free of interference from other threads.
// 标记当前结点是CANCELED
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
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.
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 {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}