2020年6月24日 作者 zeroheart

AQS的初步理解

AQS-抽象队列式同步器,常用的同步类,ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier等都用到AQS

由一个volatile的state,和一个FIFO的队列组成

这里volatile能够保证多线程下的可见性,当state=1则代表当前对象锁已经被占有,其他线程来加锁时则会失败,加锁失败的线程会被放入一个FIFO的双向等待队列中,比列会被UNSAFE.park()操作挂起,等待其他获取锁的线程释放锁才能够被唤醒。

state状态修改是通过cas实现的。

下图可以简单概括

非公平的设置下,当线程竞争的时候,首先修改state为1的线程获得锁,并把head指向该线程,setExclusiveOwnerThread为当前线程,在可重入的情况下,每次重入state+1。锁释放的时候,会唤醒head节点的后续线程,开始竞争锁,成功后会把原来的线程node节点从head和后续节点断开,等待垃圾回收。以上是没有其他新的竞争的情况,如果等待节点被唤醒,同时也有新的线程加入竞争,那么此时获得锁的不一定是排在前面的节点。这是非公平锁的实现。

公平锁的设置下,如果队列中有元素等待,那么新的线程直接进入等待队列的尾部,具体的区别就在与tryAcquire()方法加了判断

if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}

如果hasQueuedPredecessors()返回false,表示没有等待线程,那么才参与竞争。

public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
   return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());
}
之前写过一篇,这次加了点图,方便理解。
https://www.zeroheart.xyz/wordpress/?p=283
更多的细节,可以参考一下这篇
https://mp.weixin.qq.com/s/trsjgUFRrz40Simq2VKxTA

AQS中的等待唤醒,是用locksupport来实现的,park-unpark替代了obj的wait-notify机制

传统的wait-notify和lock的await,signal都需要 1.获取锁,才能调用,2.都需要先wait才能唤醒,而locksupport不同可以不用在lock块中执行,也可以先唤醒在等待。

调用unpark就会给permit+1=1,调用park就会消费一次1->0,立刻返回,如果原来就是0,那就阻塞等待unpark

此图片的alt属性为空;文件名为image-22-1024x383.png



此图片的alt属性为空;文件名为image-19.png
此图片的alt属性为空;文件名为image-20-1024x242.png
每个排队的线程也有一个状态

此图片的alt属性为空;文件名为image-21-1024x546.png
队列为空的时候,第一个等待线程入队,会先增加一个空的节点作为头部

尚硅谷Java大厂面试题全集(java面试,周阳主讲)-Java面试_大厂高频面试题_阳哥_哔哩哔哩_bilibili