深入理解AQS
示例代码
/**
* @ClassName AQSDemo
* @Description TODO
* @Author Oneby
* @Date 2021/1/21 11:08
* @Version 1.0
*/
public class AQSDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
// 带入一个银行办理业务的案例来模拟我们的AQS如何进行线程的管理和通知唤醒机制
// 3个线程模拟3个来银行网点,受理窗口办理业务的顾客
// A顾客就是第一个顾客,此时受理窗口没有任何人,A可以直接去办理
new Thread(() -> {
lock.lock();
try {
System.out.println("-----A thread come in");
try {
TimeUnit.MINUTES.sleep(20);
} catch (Exception 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();
}
}
1.new ReentrantLock()
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
如果使用默认构造方法,创建的是非公平锁;
如果参数为false,创建的是非公平锁;
如果参数为true,,创建的是公平锁;
2.ReentrantLock.lock()
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquir##e(1);
}
第一次执行lock()方法,state变量值为0,表示lock没有被占用,使用CAS将state变量值变为1,占有锁;
如果在执行lock方法时、其他拥有锁的线程刚好释放锁或者锁处于空闲状态、则直接通过CAS修改state变量、表示占有锁;
非公平锁lock()方法上来就尝试抢占一次锁、而非公平锁不会。
2.1 compareAndSetState(0, 1)
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
其实就是调用Unsafe类compareAndSwapInt方法,判断state变量的值是否为0,如果为0,就改为1。如果state不为0,则返回false,表示CAS修改失败;
在这里第一个线程A进来后,就将state改为了1,返回true,并调用 setExclusiveOwnerThread(Thread.currentThread()),将占有锁的线程改为当前线程;
线程B,C进行CAS修改state值,由于state为1,所以CAS修改失败,返回false,进入acquire();
3. acquire()
AbstractQueuedSynchronizer.acquire()
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这个方法包含了AQS获取锁、将获取不到锁的线程入同步队队的流程;
4. tryAcquire(arg)
默认arg的值为1;
AbstractQueuedSynchronizer.tryAcquired(arg)
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
这里使用了模板设计方法,如果子类不重写这个方法,调用这个方法时,就会抛出异常;
NoFairLock.tryAcquire(arg)
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
非公平锁的tryAcquire()最后会调用nonfairTryAcquire方法;
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取当前线程
int c = getState();
//获取state的值;
if (c == 0) {
//如果占有锁的线程在此时释放了锁、那么state的值为0,其他线程可以尝试占有锁;(不可能一直处于空闲,否则前面lock方法就占有锁了)
//对应代码的情况就是:线程A在此刻运行结束、而线程B、C同时执行这个方法、通过CAS尝试去占有锁、将state变为1;
if (compareAndSetState(0, acquires)) {
//通过CAS去修改state的状态,因为可能会有多个线程同时修改。
setExclusiveOwnerThread(current);
//设置当前线程为占有锁的线程;
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//判断当前线程是否是持有锁的线程,如果是,则表示为可重入锁,将state的值+1;
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//state的不为0,且占有锁的线程不是当当前线程,则直接返回false;
return false;
}
- nonfairTryAcquire方法里,上来先判断一下state的值,为0,则抢占锁;
- 如果state不为0, 就判断当前线程是否是持有锁的线程,如果是,则表示为可重入锁,将state的值+1;
- state的不为0,且占有锁的线程不是当当前线程,则直接返回false;
回到acquire()方法里,执行下一步;
即addWaiter(Node.EXCLUSIVE)
5. addWaiter(Thread)
这个方法将获取不到锁的线程封装为Node结点,装入队列;
private Node addWaiter(Node mode) {
//1、 将当前结点封装为一个Node结点
//mode为独占式Exclusive模式;
Node node = new Node(Thread.currentThread(), mode);
//获得同步队列的尾结点tail;
//如果已经有队列中已经有Node结点了,则tail不为空;
//由于我们线程A获得锁、其他线程里的第一个线程运行到这里的时,同步队列是空的,所以tail为null; 先执行enq方法;
Node pred = tail;
//pred指向tail指针指向的结点。
if (pred != null) {
//如果tail尾指针不为空,则将当前结点的前向指针指向尾指针所指向的结点。
node.prev = pred;
if (compareAndSetTail(pred, node)) {
//再通过CAS将尾指针指向当前结点。
//将结点的前一个结点的next指针指向当前结点。
pred.next = node;
return node;
}
}
//如果此时CLH队列里面没有结点、即head=null,tail=null,就会执行enq创建傀儡结点,这个方法可能会有多个线程同时进入。
enq(node);
//返回当前结点。
return node;
}
5.1 enq(Node node)
可能会有多个线程进入这个方法,所以需要自旋和CAS即自旋锁来保证原子性;
private Node enq(final Node node) {
//自旋操作,可能多个线程同时进来
for (;;) {
//先获得队列尾结点tail;
Node t = tail;
//刚开始tail为空,判断成立
if (t == null) { // Must initialize
//多个线程可能同时进来,只有一个线程使用CAS创建一个傀儡结点;
//其他线程CAS设置失败,返回false,跳出if方法体;
//new Node(),是一个Thread为null的结点,也称为傀儡结点,使用CAS设置Head头结点;
if (compareAndSetHead(new Node()))
//然后再将头结点的引用设置为tail尾结点。
//即使tail结点指向傀儡结点。
tail = head;
} else {
//因为可能多个线程同时进入了这个方法,由于第一个线程创建了傀儡结点,并通过CAS将tail和head结点指向了傀儡结点。
//所以tail不为空,其他的线程会将Node结点接入傀儡结点的后面
node.prev = t;
//将结点的前向指针指向队列的尾指针所指向的结点。
if (compareAndSetTail(t, node)) {
//再通过CAS将尾指针指向当前结点。
//将结点的前一个结点的next指针指向当前结点。
t.next = node;
//返回当前结点。
return t;
}
}
}
}
5.1.1compareAndSetHead
判断头结点是否为null,如果为null,就把Update对象引用设置为头结点。
这里的Update对象引用是傀儡结点的引用
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
这样addWaiter执行结束,线程都封装成Node结点,入队了;
6. acquireQueued(Node node, arg)
这里的arg默认为1;
这个方法的作用:修改Node结点的waitStatus状态、使用LockSupport.unpark()阻塞线程,并等待唤醒;
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//自旋操作;
for (;;) {
//predecessor获取当前结点的头一个结点;
final Node p = node.predecessor();
//判断P是否为头结点 且 尝试去获取获取一下锁
//判断state状态是否为0,是则获取锁
//如果占有锁的线程再这一刻释放了锁,那么队列的头结点会被唤醒
//并且尝试去获取锁。
if (p == head && tryAcquire(arg)) {
//如果获取了锁,将当前结点设置为傀儡结点。
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果锁没有释放,上面判断不成立,则进行下面的逻辑;
//执行shouldParkAfterFailedAcquire将当前结点的上一个结点的waitStatus
//改为Signal,返回false;在循环执行一次,shouldParkAfterFailedAcquire()
//返回true;再接着去执行parkAndCheckInterrupt方法,将线程阻塞住。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
6.1 Node.predecessor()
获取当前结点的上一个结点;
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
6.2 shouldParkAfterFailedAcquire(p,node)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取当前结点的上一个结点的waitStatus;
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//如果是-1,即Signal状态,则是可被唤醒装填。可以获得锁;
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//这里通过CAS将上一个结点的waitStatus改为Signal装填;
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//返回false;
return false;
}
6.3 parkAndCheckInterrupt()
使用LockSupport的park方法将线程阻塞住。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
7. ReentrantLock.unlock()
线程A执行结束,释放锁了。
//该方法为ReentrantLock中定义的。
public void unlock() {
sync.release(1);
}
8 sync.release() 方法的执行流程
public final boolean release(int arg) {
//tryRelease释放锁,将state变为1,并将占有锁的线程设置为null;
if (tryRelease(arg)) {
//获取队列头指针所指向的结点。
Node h = head;
//如果头指针指向的结点对象不为空,且结点的waitStatus状态为Signal,即-1;则唤醒头结点。
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
//唤醒头结点的下一个结点。
return true;
}
return false;
}
8.1 tryRelease(arg)
将执行的线程设置为Null,再将state的值变为0
protected final boolean tryRelease(int releases) {
//获取state的值给C,并设置为0,
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
//C=0;再将占有锁的线程设置为null;
free = true;
setExclusiveOwnerThread(null);
}
//复位state的值,即0;
setState(c);
return free;
}
8.2 unparkSuccessor(h)
唤醒傀儡结点的下一个结点,并将傀儡结点的下一个结点的等待状态waitStatus设置为0
//node:传入的是队列头指针所指向的结点对象、即傀儡对象;
private void unparkSuccessor(Node node) {
//获取傀儡对象的等待状态waitStatuS
int ws = node.waitStatus;
//如果waitStatus小于0,则说明是Signal状态
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//将其waitStatus通过CAS改为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)
LockSupport.unpark(s.thread);
//如果傀儡结点的下一个结点存在,不为Null,则将这个结点里面封装的线程唤醒
}
9 unpark
唤醒结点后,回到acquireQueued()方法,
线程被阻塞在parkAndCheckInterrupt()方法里。
parkAndCheckInterrupt():
private final boolean parkAndCheckInterrupt() {
//线程被阻塞在这里。
LockSupport.park(this);
return Thread.interrupted();
}
当线程被唤醒后,Thread.interrupted()由于没有发生中断,所以返回false;
继续自旋。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//自旋
final Node p = node.predecessor();
//唤醒后,再次尝试tryAcquire()方法,
//如果获取锁成功,则将傀儡结点的下一个结点变为傀儡结点。
//如果获取锁失败,因为这个时候如果有新的线程刚好申请锁,可能会被这些线程给抢走,继续执行shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法,阻塞。
if (p == head && tryAcquire(arg)) {
//如果获取锁成功,则通过将队列头指针指向当前结点(傀儡结点的下一个结点/被唤醒的结点)。
setHead(node);
//将傀儡结点的后向指针给断掉;
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
9.1 setHead(node)
将头指针指向当前结点(被唤醒的结点)。
private void setHead(Node node) {
head = node;
//将node的封装的线程设置为空;
node.thread = null;
//将当前结点的前向指针与傀儡结点断掉;
node.prev = null;
}
至此,基于ReentrantLock的AQS源码分析结束;
源码中设计CAS,LockSupport,队列,并发场景的熟练程度要求较高。