(AQS实现的)
。抢占资源失败的线程继续去等待,但等候线程仍然保留获取锁的可能且获取锁流程仍在继续。(AQS实现的)
(AQS实现的)
。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS同步队列的抽象表现。它将要请求共享资源的线程及自身的等待状态封装成队列的结点对象(Node),通过CAS、自旋以及LockSupport park()的方式,维护state变量的状态,使并发达到同步的效果。解释说明:AQS使用一个volatile的int类型的成员变量来表示周步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成- -个Node节点来实现锁的分配,通过CAS完成对State值的修改。
因为ReentrantLock就是AQS实现的,所以本次通过ReentrantLock来讲解下AQS具体的实现逻辑
从源码中我们可以看出公平锁和非公平的锁,唯一区别就在于: 公平锁多加了一个HasQueuePredecessor() 方法其他和非公平锁实现一致;
A线程第一个来, 线程没有争抢,先办理业务
第一个线程A进来了,没有和他争抢,此时抢占锁,把state状态修改为1,并赋值当前线程为抢占线程;
B线程尝试抢锁,失败了,打算进入到等待席(tryAcquire)
第二个线程B尝试抢占锁,但是CAS比较失败,进入到acquire(1),再进入到tryAcquire->子类的nonfairTryAcquire; 此时c=1 并且 getExclusiveOwnerThread != threadB 返回false
acquire源码↓↓↓↓
nonfairTryAcquire源码
B线程进入到等待席(tryAcquire->addWaiter)
线程B封装到Node节点中,检查下席位有没有数据,没有数据的话,进入到enq(node);
enq方法自己给我们New Node一个新节点[不是线程B节点哦],放到头结点;双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位。真正的第一个有数据的节点,是从第二个节点开始的。.
因为他是for(; ; ) 第二次检查,把队尾指向线程B,然后进行new Node进行cas,并且
C线程抢占锁,入队(tryAcquire->addWaiter) ; 和之前相比,因为尾结点不为null ,线程C第一次进入了compareAndSetTail
此时队列的头和node之前的顺序进行关联
B节点进入到等待席,修改前置节点的waitStatus=-1
acquire->acquireQueued->shouldParkAfterFailedAcquire
判断头节点是不是虚节点,如果是B线程再抢抢,如果抢不到则进入到shouldParkAfterFailedAcquire
此时New Node的waitStatus为0, 并不等于-1,我们进入compareAndSetWaitStatus
把前置节点(头节点)的waitStatus修改为-1
此时B线程因为因为自旋的原因;进入到了waitStates=-1(代表线程已经准备好了,就等资源释放了)的逻辑
waitStatus=-1 parkAndCheckInterrupt方法
acquire->acquireQueued->shouldParkAfterFailedAcquire && parkAndCheckInterrupt
我们可以看到如下的代码 , 从中我们得出结论AQS的线程阻塞考得是LockSupport.park方法
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
C节点进入到等待席,修改前置节点[B节点]的waitStatus=-1
acquire->acquireQueued->shouldParkAfterFailedAcquire
然后进入LockSuppport , 然后其他的线程反复进入到这个循环
1、尝试加锁;
2、加锁失败,线程入列;
3、线程入队列后,进入阻塞状态。