AQS机制-ReentrantLock实现看AQS原理
2022-06-28 19:11:44 1 举报
AQS是一个虚拟的CLH FIFO的双向队列来管理被阻塞的线程。是Java并发中用以解决多线程访问共享资源问题的同步机制的基本的框架(或者说是一种规范)
作者其他创作
大纲/内容
是
返回当前线程对应的Node
否
prev
Lock
compareAndSetHead(new Node())tail = head;初始化CLH队列,初始化傀儡节点
font color=\"#ff0000\" style=\"\
ReentrantLock
1、尝试获取锁
waitStatus=0
Thread-C
设置当前线程独占锁setExclusiveOwnerThread(Thread.currentThread());
互斥锁解锁
当前占用线程Thread = A
state==0
头节点不为null且waitstats不等于0
lock()
获取当前节点的后继节点(哨兵节点的下一个节点)Node s = node.next;
new Node()
state状态位
成功释放锁
head
!hasQueuedPredecessors()判断CLH队列是否存在等待线程
自旋保证全部入队,添加到尾部,返回当前B节点
int c = getState() - releases;计算新的state,如果当前线程不是锁占用线程则抛异常
被占用
acquireQueued
获取锁失败返回false
next
unpark唤醒线程B后,自旋操作
核心机制:尝试获取锁,如果获取不到,就进行排队并阻塞当前线程(LockSupport#park)
addWaiter入队流程
sync.release(1);
自旋,保证全部入队
当前占用线程Thread = B
B线程执行enq方法,此时tail节点指向还是null,进入if执行初始化,设置Head节点,初始化完成后设置尾节点等于头节点
Thread-B
同步器
tryRelease执行后结果图
AQS = state状态 + CLH队列(FIFO)
队列初始化的一个节点,节点对应线程为null,waitStatus为默认值0该节点叫哨兵节点或者傀儡节点
lock.unlock()
head指向为傀儡节点,且waitStatus=-1执行unparkSuccessor(h)方法
setExclusiveOwnerThread(current);设置当前线程独占锁
添加至CLH队列尾部
C线程执行addWaiter时tail节点为B不为null,添加C节点入队,返回C节点
返回当前线程是否中断,结束自旋
状态位State = 0
tryRelease(arg)锁成功释放
enq(node);添加到队列中
将当前线程对应的node节点从CLH队列中移除,并重新设置队列的傀儡节点
CAS自旋
tail == null判断尾节点为空
setExclusiveOwnerThread(null);设置当前占用线程为nullfree = true
获取锁成功
从尾节点开始,从后向前遍历,查找等待状态小于等于0的节点(节点状态正常)
否,自旋尝试获取锁
sync.lock()
判断当前队列是否有等待线程if (pred != null)
自旋,进行设置B节点
线程初始化之后,state值默认为0,标识没有线程占用
tail
ws < 0节点等待状态小于0
获取锁是否成功
3、CLH队列中,等待获取锁
unparkSuccessor(h);唤醒节点的后继节点(如果存在)
自旋node.predecessor() 获取当前线程B节点的prev节点,线程B的prev节点是傀儡节点,且傀儡节点就是head节点,继续尝试获取锁,由于此时锁被线程A占用,if语句无法执行
用户线程第一次获取锁失败之后,进入CLH队列,此时用户可能会中断该线程,所以线程从CLH队列被唤醒之后,要先检查一下之前有没有被中断过,如果中断过了,此时再中断线程。
!tryAcquire(arg)
第一次进入线程B的pred节点waitStatus=默认值0执行cas设置waitStatus=-1返回false,再次进行自旋
private volatile int state;
抢占失败后执行shouldParkAfterFailedAcquire方法
selfInterrupt();Thread.currentThread().interrupt();设置打断标记,将终止信号外传
第二次进入线程B的pred节点waitStatus=-1返回true
addWaiter(Node.EXCLUSIVE)
Thread-null
getState() == 0锁是否被占用
状态位State = 1
return free
不应该阻塞,自旋尝试获取锁
是否添加成功
false
tail = null
cas尝试获取锁
3、线程B、C执行NonfairSync#lock() 获取锁
true
执行parkAndCheckInterrupt()当前线程被阻塞,等待被唤醒
return true
2、线程A--执行NonfairSync#lock() 获取锁成功
调整waitStatus值为-1
判断线程需不需要排队,因为队列是FIFO的,所以需要判断队列中有没有相关线程的节点已经在排队了面试可能有坑
unlock
B线程执行lock方法,进行cas自旋失败,进入tryAcquire方法,尝试获取锁1、此时state状态为1,被线程A所占用,则上述if语句并不会执行,b style=\
tryRelease(arg)
p == head判断当前节点是否为头节点
Node3
阻塞的B线程,被唤醒
此时CLH队列为空,tail节点指向为null,所以不进入if语句执行enq(node)方法
acquire(1)
waitStatus=-1
唤醒该节点LockSupport.unpark(s.thread);解除s.thread的阻塞
返回true
总结:1、锁没有被占用1)公平锁判断是否有等待线程,没有等待线程再获取锁2)非公平锁直接CAS自旋尝试获取锁2、锁被占用判断占用线程是否是当前线程,如果是当前线程则更改state值。(体现了AQS的可重入特性)
获取锁失败
演示A、B、C三个线程使用ReentrantLock的过程
应该阻塞
NonfairSync#lock()非公平锁
head = null
线程尝试获取锁失败后,会将当前线程添加到CLH队列,如果队列中有等待线程节点,则添加到队列尾部,如果没有则先检查是否初始化,进行先初始化后添加的操作。
演示A线程unlock,B线程获取锁的操作
当前占用线程Thread = null
1、设置head节点为线程B节点2、设置线程B节点的thread=null3、设置线程B节点的prev节点为null4、设置傀儡节点的next节点为null5、return false其实就是让傀儡节点被GC回收掉,由于线程B节点已经抢到了锁,所以线程B无需存在队列中,将当前队列中的线程B节点变成傀儡节点
方法作用:主要是用来判断线程需不需要排队,因为队列是FIFO的,所以需要判断队列中有没有相关线程的节点已经在排队了。有则返回true表示线程需要排队,没有则返回false则表示线程无需排队。无需排队的情况分析:一、 h != t 返回false1、头节点和尾节点都为null,表示队列都还是空的,甚至都没完成初始化,那么自然返回fasle,无需排队。2、头节点和尾节点都不为null但是相等,说明头节点和尾节点都指向一个元素,表示队列中只有一个节点,自然无需排队,队列中的第一个节点就是傀儡节点二、 h != t返回true,(s = h.next) == null返回false并且s.thread !=Thread.currentThread()返回false1、 h != t返回true表示队列中至少有两个以上的不同节点存在。2、 (s = h.next) == null返回false表示head节点是有后继节点的。3、 s.thread != Thread.currentThread()返回fasle表示着当前线程和后继节点的线程是相同的,那就说明已经轮到这个线程相关的节点去尝试获取同步状态了,自然无需排队,直接返回fasle。
Node2
锁释放失败返回false
2、尝试获取锁失败后,添加当前线程到CLH队列中,以EXCLSIVE独占模式
自我中断设置打断标记
线程C入队
compareAndSetTail()
后继节点不为空且waitstatus大于0
parkAndCheckInterrupt()阻塞当前线程如果被打断,返回线程中断状态
1、初始化
current == getExclusiveOwnerThread()判断是否当前线程占用
A线程执行lock方法,进行cas自旋成功,设置state值为0,当前占用线程为A
1、head节点的waitStatus为-1小于0,cas更新waitStatus=02、if条件不满足,执行ThreadB的unPark(node.next)
Node1
int nextc = c + acquires;setState(nextc);更改state值
setHead()
入CLH队列后,尝试获取锁,先判断是否为头节点,是的话尝试获取锁,没有获取到锁并且不是头节点的时候,并且不是阻塞的时候,进行自旋尝试获取锁,如果需要阻塞则阻塞当前线程,唤醒后,如果中途被打断则返回当前中断状态
它将请求共享资源的线程封装成队列的节点(Node),通过CAS自旋及LockSupport的park、unpark的方式维护state变量值,使并发达到同步控制的效果Node对象封装的是请求的线程
当前占用线程Thread = Null
setState(c);设置新的状态标志位statefree = false
判断锁类型
cas恢复初始状态0如果状态为负(哨兵节点)
没有被占用
自旋node.predecessor() 获取当前线程B节点的prev节点,线程B的prev节点是傀儡节点,且傀儡节点就是head节点,继续尝试获取锁,此时状态为State=0,占用线程为null,尝试获取锁成功
执行addWaiter(Node.EXCLUSIVE)添加当前B线程到CLH队列
傀儡节点被GC回收
tryAcquire(arg)尝试获取锁
是否公平锁
线程B入队
获取锁成功返回true
FairSync#lock()公平锁
enq(node)初始化队列
0 条评论
下一页