AbstractQueuedSynchronizer
是java.util.concurrent
包下非常重要和基础的类,concurrent包下提供了一系列的同步操作需要的工具,包括了ReentrantLock
、ReentrantReadWriteLock
、ThreadPoolExecutor
、CountDownLatch
、LinkedBlockingQueue
等等,该包下很多的工具类都直接或者间接基于AQS提供的独占锁、共享锁和等待队列实现了各自的同步需求。
一、AQS的设计:①AQS将资源抽象成一个int型的值:int state
,通过对state的修改,达到对AQS不同状态的控制,对state变量的修改由其子类实现;②并不是state>0才代表有资源可以申请,如ReentrantLock
中只有state==0的时候才获取成功,state>0代表资源被占用,什么情况代表有资源可用是子类根据state的值做的一个抽象而已;③AQS的两个方法:acquire(int arg)、release(int arg)
,分别用来获取和释放一定量的资源,即增大和减小state的值。当线程执行上述两个方法时,AQS的子类尝试修改state的值;④在acquire()
方法中:若state大小符合特定需求(具体逻辑由子类实现),则线程会锁定同步器,否则将当前线程加入到同步队列中;在release()
方法中:若state大小符合特定需求,则释放掉当前线程占有的资源,唤醒同步队列中的线程。
二、通过ReentrantLock
的源码分析独占锁:ReentrantLock
中对state的设计为:state==0的时候代表没有线程持有同步器,在state>0的时候,其它线程是不能获取同步器的,必须加入到同步队列中等待。
1、从lock()
方法开始:这里只分析NonfairSync
static final class NonfairSync extends Sync {
//从这个方法开始申请锁
final void lock() {
//当前state的值为 0,线程直接获取到锁,setExclusiveOwnerThread()方法可以设置当前持有锁的线程
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//说明已经有线程持有了锁,则acquire()申请资源,acquire()函数的作用就是:
//首先调用tryAcquire(),尝试获取资源,在此的具体实现就是nonfairTryAcquire()方法
//获取资源失败则将线程加入到同步队列中等待
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
//尝试获取资源的地方
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//首先还是判断是否有线程已经持有锁(c=0),还未有线程持有锁则让该线程持有锁,设置state的值为acquires,返回true
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//已经有线程持有锁,判断持有锁的线程和申请锁的线程是否是同一个线程,如果是,让state值增加acquires,返回true
//也就是同一个线程可以重复获取到同步器,从这里可以看出为什么ReentrantLock锁是可重入的了
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//不是上面两种情况,即获取资源失败,返回false
return false;
//判断获取到锁的是不是当前线程
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
//获取Condition实例,ConditionObject的定义在AQS中
return new ConditionObject();
}
final Thread getOwner() {
//获取当前持有锁的线程,state值为 0,代表还未有线程持有锁
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//state不为0,就代表有线程持有锁了
final boolean isLocked() {
return getState() != 0;
}
}
2、Node
类:是同步队列中节点的描述,用来保存等待的线程、状态、前驱和后继节点等的信息。
static final class Node {
//标识共享模式的节点,共享模式下Node节点的nextWaiter变量设置为这个值
static final Node SHARED = new Node();
//标识独占模式的节点,独占模式nextWaiter变量是null
static final Node EXCLUSIVE = null;
//同步队列中被取消的节点,被中断或者等待超时,该状态的节点不再被使用
static final int CANCELLED = 1;
//标识后继节点处于被唤醒的状态,当节点释放同步器后,会唤醒后继节点中第一个处于该状态的节点
static final int SIGNAL = -1;
//描述处于等待队列中的节点,节点中的线程等待在Condition上,
//在调用signal()之后,被唤醒的节点将从等待队列中转移到同步队列中继续等待
static final int CONDITION = -2;
//确保共享模式下可以唤醒后续的共享节点
static final int PROPAGATE = -3;
//保存节点状态,值为 0、CANCELLED、SIGNAL、CONDITION、PROPAGATE
volatile int waitStatus;
//链表的上一个节点
volatile Node prev;
//链表的下一个节点
volatile Node next;
//保存被阻塞的线程
volatile Thread thread;
//用来保存某个Condition上的等待队列,也用来判断节点是否是共享节点
Node nextWaiter;
//判断节点是否是共享模式,通过判断nextWaiter==SHARED
final boolean isShared() {
return nextWaiter == SHARED;
}
//找到前驱节点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {}
Node(Node nextWaiter) {
this.nextWaiter = nextWaiter;
U.putObject(this, THREAD, Thread.currentThread());
}
Node(int waitStatus) {
U.putInt(this, WAITSTATUS, waitStatus);
U.putObject(this, THREAD, Thread.currentThread());
}
/** CAS设置节点的状态 */
final boolean compareAndSetWaitStatus(int expect, int update) {
return U.compareAndSwapInt(this, WAITSTATUS, expect, update);
}
/** CAS设置后继节点 */
final boolean compareAndSetNext(Node expect, Node update) {
return U.compareAndSwapObject(this, NEXT, expect, update);
}
//Unsafe 是CAS的工具类,CAS是虚拟机实现的原子性操作
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
private static final long NEXT;
static final long PREV;
private static final long THREAD;
private static final long WAITSTATUS;
static {
try {
//分别拿到Node节点的next、prev、thread、waitStatus变量的句柄,CAS通过句柄修改这些变量
NEXT = U.objectFieldOffset
(Node.class.getDeclaredField("next"));
PREV = U.objectFieldOffset
(Node.class.getDeclaredField("prev"));
THREAD = U.objectFieldOffset
(Node.class.getDeclaredField("thread"));
WAITSTATUS = U.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
}
3、AQS的acquire(int arg)
方法:从NonfairSync
的实现看出,申请锁(资源)的时候,首先会执行acquire()
方法。
①acquire()
方法,尝试获取资源,失败将线程加入同步队列中
public final void acquire(int arg) {
//tryAcquire()尝试获取资源,返回false,执行acquireQueued()方法,将线程加入同步队列中,让线程进入等待状态
//addWaiter()这里传入的是独占标志(Node.EXCLUSIVE),说明该节点是一个独占节点
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
②addWaiter()
方法,将当前线程封装进Node节点,并将节点加入到同步队列中,如果头结点为null,则初始化头结点和尾节点,并将当前节点链接在尾节点之后。
private Node addWaiter(Node mode) {
//初始化一个Node节点,变量nextWaiter为mode
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
//将当前节点设置成尾节点,链接在之前的尾节点之后
U.putObject(node, Node.PREV, oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
//初始化头节点和尾节点指向同一个节点
initializeSyncQueue();
}
}
}
③acquireQueued()
方法,自旋直到线程持有同步器,这里线程将进入等待状态,直到其它线程释放资源,唤醒处于队首的线程,唤醒后该线程必须要继续获取到资源
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
//自旋,直到线程持有同步器,(tryAcquire()成功)
for (;;) {
final Node p = node.predecessor();
//该节点的前驱是头节点,说明唤醒顺序先进先出的
//尝试获取资源,成功则返回interrupted,让该线程持有同步器
if (p == head && tryAcquire(arg)) {
setHead(node); //设置节点为头节点
p.next = null; // 帮助释放内存
return interrupted;
}
//检查节点状态,如果可以进入等待状态,则让线程进入等待状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
④shouldParkAfterFailedAcquire()
方法,设置节点的状态,要为node节点找到一个符合需求的前驱节点,并设置其状态为SIGNAL,表明node节点是一个在等待状态的正常的节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//前驱节点的waitStatus为SIGNAL,说明后继节点可以进入等待状态
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
//前驱节点是CANCELLED,则跳过这些节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//前驱节点状态正常的情况下,设置状态为SIGNAL,标志后继节点可以进入等待状态
pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
}
return false;
}
⑤parkAndCheckInterrupt()
方法:借助LockSupport
将当前线程阻塞,在唤醒之后返回中断状态,因为在阻塞状态下,线程不响应中断,在唤醒之后,如果有需要则重新处理该中断。
private final boolean parkAndCheckInterrupt() {
//LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程
//其它说明可百度
LockSupport.park(this);
return Thread.interrupted();
}
4、AQS的release(int arg)
方法:acquire()
方法完成了资源获取成功后持有同步器,获取失败后加入到同步队列中,并将线程阻塞等一系列操作。下面分析同步队列线程被唤醒的过程。
①ReentrantLock
的unlock()
方法只调用了release(1)
方法:tryRelease()
由子类实现,用来判断是否可以唤醒线程。如果可以则调用unparkSuccessor()
唤醒一个线程
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
//这里传入的是头结点,也就是说唤醒的是头结点的后继节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
②ReentrantLock
的tryRelease()
方法:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//state==0才能唤醒同步队列中的线程
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//重新设置state的值
setState(c);
return free;
}
③ unparkSuccessor(Node node)
方法:
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
node.compareAndSetWaitStatus(ws, 0);
//找到需要被唤醒的节点
Node s = node.next;
//如果当前节点的后继节点不符合要求(为null或者被取消了),则从尾节点向前查找
if (s == null || s.waitStatus > 0) {
s = null;
for (Node p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
//唤醒该线程,线程的阻塞是在acquireQueued()方法中,线程将从阻塞的地方继续执行
if (s != null)
LockSupport.unpark(s.thread);
}
三、通过CountDownLatch
分析共享锁:CountDownLatch
在释放同步器之后会一次唤醒所有等待在同步队列上的线程。主要功能是:允许一个或者多个线程等待其他线程完成任务,然后这些等待的线程同时被唤醒,使用很简单,下面主要分析共享锁的实现,在ReentrantLock
中分析过的函数不再分析。
1、CountDownLatch
中的自定义同步器:
//CountDownLatch的构造函数,传入了一个int值,作为state的初始值
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
//设置state的初始值为自己传入的参数
setState(count);
}
int getCount() {
return getState();
}
//只有state为 0的时候,也就是其它线程全部执行完成后,才算获取资源成功,返回1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
//只有在state==1的时候才会返回true,进而释放同步器,唤醒等待的线程
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
2、CountDownLatch
中的await()
方法:获取资源,只调用了AQS的acquireSharedInterruptibly()
方法,该方法会抛出中断异常,在调用await()
时要捕获异常。
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取资源,返回int型值,tryAcquire()返回bool值
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
3、doAcquireSharedInterruptibly()
方法:
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//将一个共享节点添加到同步队列中
final Node node = addWaiter(Node.SHARED);
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
//尝试获取资源成功后(r>=0),setHeadAndPropagate()将head节点指向node,然后唤醒同步队列中的共享节点
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
return;
}
}
//修改节点的状态,阻塞线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果等待过程中产生了中断,则抛出中断异常
throw new InterruptedException();
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
4、setHeadAndPropagate()
方法:设置头结点为node节点,并尝试唤醒后续的共享节点
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
//是共享节点尝试释放资源
if (s == null || s.isShared())
doReleaseShared();
}
}
5、countDown()
方法释放资源:该方法中只调用了releaseShared(1)
方法。
public final boolean releaseShared(int arg) {
//尝试释放指定量的资源,成功之后,doReleaseShared()唤醒同步队列中的线程
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
6、doReleaseShared()
方法:唤醒后续的节点,
private void doReleaseShared() {
//自旋确保在CAS失败的情况下可以再次执行
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
//头结点的后继节点在等待状态,则唤醒后继节点
if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
continue;
unparkSuccessor(h);
}
else if (ws == 0 &&
!h.compareAndSetWaitStatus(0, Node.PROPAGATE))
continue;
}
if (h == head)
break;
}
}
总结:doAcquireShared()
请求资源,失败则将一个共享节点加入到同步队列, releaseShared()
方法释放资源,成功后setHeadAndPropagate()
方法中将唤醒后续的共享节点。
四、简单通过LinkedBlockingQueue
分析Condition的实现:BlockingQueue
接口是做等待队列用的,在线程池中就有过使用(ThreadPoolExecutor的使用及源码分析)。借助Condition实现了生产-消费者模式,生产消费线程将阻塞在Condition队列上面,
1、通过take()
方法分析阻塞和唤醒线程的过程:
//新建ReentrantLock 锁
private final ReentrantLock takeLock = new ReentrantLock();
//新建Condition 实例,是通过ReentrantLock的newCondition()方法
private final Condition notEmpty = takeLock.newCondition();
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
//在执行await()或者signal()方法前必须先获取锁,在后面的分析中会看到原因
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
//将线程加入到notEmpty等待队列中
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
//从notEmpty等待队列中唤醒一个线程
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
2、await()
方法:将线程加入的某个Condition的等待队列上,然后让线程进入等待状态,等待signal()
的唤醒,然后被移入到同步队列。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//新建Node节点添加到等待队列中
Node node = addConditionWaiter();
//当线程进入等待队列的时候,会释放之前申请的资源,这时候会唤醒同步队列上的其它线程
int savedState = fullyRelease(node);
int interruptMode = 0;
//节点不在同步队列上,则阻塞该线程,等待signal()方法的唤醒,
//被唤醒后,节点将从等待队列中被转移到同步队列中,然后跳出这个循环
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//线程被唤醒之后,要去申请资源,该方法之前分析过,只有申请到资源才能唤醒线程,否则会被再次阻塞
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//释放所有资源
final int fullyRelease(Node node) {
try {
int savedState = getState();
//释放资源,从这里可以知道,在没有持有同步器的情况下,执行await()方法会报IllegalMonitorStateException错误
if (release(savedState))
return savedState;
throw new IllegalMonitorStateException();
} catch (Throwable t) {
node.waitStatus = Node.CANCELLED;
throw t;
}
}
3、signal()
方法:唤醒一个等待队列中的节点
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//拿到等待队列中的头节点去唤醒
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
//等待队列中的头结点替换为后继节点
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
4、transferForSignal()
方法:将等待队列中的一个节点添加到同步队列的队尾,并唤醒该线程,让线程进入到acquireQueued()
方法自旋申请资源。到这里signal()
方法执行完成,在takeLock.unlock()
之后,会重新从同步队列中唤醒队头节点,这样就完成了一次等待–唤醒的流程。
final boolean transferForSignal(Node node) {
//将节点的CONDITION状态设置为0(初始状态)
if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
return false;
//将等待队列的节点放入到队尾,返回的是之前的队尾节点
Node p = enq(node);
int ws = p.waitStatus;
//设置之前队尾节点状态为SIGNAL,然后唤醒等待队列中的线程,node节点进入acquireQueued()自旋
if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
总结:await()
会将线程阻塞,节点放入到等待队列中,signal()
会将等待队列的首节点移到同步队列中,会唤醒一次该节点,该节点进入到acquireQueued()
自旋,在释放锁之后,会再次释放资源,唤醒同步队列中的节点。