大家好,我是小趴菜,在高并发编程中,AbstractQueuedSynchronizer(简称AQS)抽象的队列同步器是我们必须掌握的,AQS底层提供了二种锁模式
- 独占锁:ReentrantLock就是基于独占锁模式实现的
- 共享锁:CountDownLatch,ReadWriteLock,Semplere都是基于共享锁模式实现的
接下来我们通过ReentrantLock底层实现原理来了解AQS独占锁模式的实现原理
ReentrantLock用法
我们创建三个线程去获取资源,然后执行业务逻辑
ReentrantLock lock = new ReentrantLock();
new Thread(() -> {
try{
lock.lock();
TimeUnit.SECONDS.sleep(10);
}catch (Exception e){
}finally {
lock.unlock();
}
},"t1").start();
new Thread(() -> {
try{
lock.lock();
TimeUnit.SECONDS.sleep(10);
}catch (Exception e){
}finally {
lock.unlock();
}
},"t2").start();
new Thread(() -> {
try{
lock.lock();
TimeUnit.SECONDS.sleep(10);
}catch (Exception e){
}finally {
lock.unlock();
}
},"t3").start();
首先分析一下获取锁的原理,也就是 lock.lock()方法
public void lock() {
//继续进入这个方法
sync.lock();
}
进入这个方法的时候有两个实现类,一个是公平锁FairSync,还有一个就是非公平锁NonfairSync,由于非公平锁比公平锁复杂,所以我们先分析非公平锁的原理
final void lock() {
//这里就会尝试去获取锁,如果成功获取到了锁,此时state的值就会从0变成1
if (compareAndSetState(0, 1))
//就把exclusiveOwnerThread的值设置成当前线程
//exclusiveOwnerThread是AQS里面的一个变量,也就是线程获取到了锁之后,就会把这个值设置成当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 如果没有获取到锁,就执行下面这个方法
acquire(1);
}
第一个线程进来之后,由于state等于0,那么就可以获取到锁,于是就会把state的值设置成1,然后exclusiveOwnerThread值设置成自己的线程
这时候第二个线程进来,发现state的值已经是1了,所以就会进入到acquire(1);这个方法了
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这里一共有三个方法,我们先分析tryAcquire(arg);这里的参数值等于1
//还是进入到非公平锁的实现类NonfairSync
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//拿到当前线程
final Thread current = Thread.currentThread();
//获取到state的值,由于之前已经有线程获取到锁了,所以这个值现在等于1,也就不会进入带if分支
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;
}
//所以最后返回false
return false;
}
所以tryAcquire(arg)方法做了以下几个步骤
- 1:继续尝试获取锁,如果获取到锁了就直接返回
- 2:如果没有获取到锁,就判断此时获取到锁的线程是不是自己,如果是,就可以获取到资源继续执行,也就是可重入锁的原理
- 3:如果以上二个步骤都不符合,就直接返回false
第一个tryAcquire(arg)方法最后返回false,但是这里是取返,所以是为true,所以就会进入acquireQueued()方法,但是在这里还有个addWaiter(Node.EXCLUSIVE), arg)方法,所以我们先分析这个方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //Node.EXCLUSIVE表示是独占模式
selfInterrupt();
}
private Node addWaiter(Node mode) {
//将当前线程封装成一个Node节点,并设置成独占模式,
//注意:一个新的Node节点的waitStatus的值等于0
Node node = new Node(Thread.currentThread(), mode);
//在第一次进来的时候,tail和head都是null,所以不会进入到if分支里面去
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//由于head和tail都为null,所以会进入初始化链表的方法
//初始化链表
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) { //注意:这里是死循环
Node t = tail;
//第一次进来,因为tail=null,所以会进入到if里面去
if (t == null) { // Must initialize
//这里新创建一个空的Node节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- 第一次进来:因为第一次进来的时候tail=null,所以会进入到if中去,然后创建一个新的空的节点,然后将头节点和尾节点都指向这个节点
- 然后进入第二次循环:这时候tail已经不为空了,所以会进入到else分支里面去,所以的操作就是将当前线程封装成的Node设置尾巴节点,然后设置前置节点和后置节点的关系
这时候第三个线程进来,然后假设也没有获取到锁,那么也会进入到addWaiter()方法中
private Node addWaiter(Node mode) {
//将当前线程封装成一个Node节点,并设置成独占模式,
//注意:一个新的Node节点的waitStatus的值等于0
Node node = new Node(Thread.currentThread(), mode);
//这时候tail和head都不为null了,所以就会进入if分支
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//由于tail和head都不为null,也就不会执行到这里来了
enq(node);
return node;
}
这时候新进来的线程就会从链表尾部插入,然后重新更新tail尾节点指向当前线程
这时候addWaiter(Node.EXCLUSIVE), arg);就会返回一个Node节点了,这个Node节点就是当前封装了当前线程的Node,比如现在第一个线程Thread-1获取到锁了,然后Thread-2进来,那么Thread-2就要进入队列中进行等待了,所以这时候返回的就是Thread-2这个Node节点。同理,第三个线程Thread-3进来,那么这时候返回的就是Thread-3这个Node节点
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //Node.EXCLUSIVE表示是独占模式
selfInterrupt();
}
现在我们先分析第二个线程Thread-2。进入acquireQueued()方法
final boolean acquireQueued(final Node node, int arg) {
//此时Node节点就是Thread-2这个线程的Node
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//拿到上一个节点,因为Thread-2是第一个排队的线程,所以他的前置节点就是头节点
final Node p = node.predecessor();
//p是头节点,所以会进入tryAcquire(arg)方法,这个方法就是再次去尝试获取锁
//但是头节点就是个虚拟节点,是不可能获取到锁的,所以不会进入到if分支里面去
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//此时会进入到这个分支里面去,此时的p是头节点,node是Thread-2的Node节点
//在第二次循环过后,shouldParkAfterFailedAcquire(p, node)会返回true
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//第一次进来,由于头节点是刚初始化的,所以waitStatus=0,所以会进入到else分支,修改头节点waitStatus
//然后由于外面的是死循环,所以会再次进入,此时头节点的waitStatus的值是Node.SIGNAL,所以会直接返回true
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//将头节点的waitStatus修改成Node.SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
由于shouldParkAfterFailedAcquire(p, node)返回true了,所以就会继续执行 parkAndCheckInterrupt()方法了
private final boolean parkAndCheckInterrupt() {
//将Thread-2线程阻塞挂起,这里为什么是this呢,因为现在执行进来的就是Thread-2线程
LockSupport.park(this);
return Thread.interrupted();
}
后续Thread-3线程进来之后,也会在这里阻塞住,然后等待被唤醒
现在Thread-1线程业务执行结束了,然后就要释放锁
lock.unlock();
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;
}
我们首先看下tryRelease(arg)方法
protected final boolean tryRelease(int releases) {
//将state的值减去1
int c = getState() - releases;
//判断当前线程是否是获取锁的那个线程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果state的值减1之后变成0了,那么就将exclusiveOwnerThread设置成null,
//因为我们state的值就是等于1,所以c=0,就会进入if分支里面去
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//将state的值设置成c
setState(c);
//因为会进入if分支,所以此时free的值等于true
return free;
}
所以tryRelease(arg)最后会返回true,于是就会进入if分支里面去
public final boolean release(int arg) {
if (tryRelease(arg)) { //返回true
//拿到链表的头节点
Node h = head;
//虽然在初始化的时候Node节点的waitStatus的值等于0,但是后面进来的线程会将前置节点的该值修改成-1
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//node是头节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus