AQS源码详解
可重入锁:同一个线程可重复获取同一把锁对象
locksupport:用来创建锁和其他同步类的基本线程阻塞原语 park()和unpark()
为什么会引出locksupport?
像传统的synchorized和lock,他们的wait()和notify()方法,await()和singal()方法使用不方便,必须在同步代码块或者锁内使用,并且先后顺序不能调用,否则会报错和出现死锁情况。
park和unpark方法
park 就是消耗当前调用该方法的线程的凭证,凭证最少为0,如果凭证为0则会进行线程的阻塞,等待凭证可用
unpark就是增加当前调用该方法的线程的凭证,凭证最多为1
锁: 面向锁的使用者
同步器:面向锁的实现者
AQS:抽象队列同步器
将阻塞等待的线程用Node结点进行封装到一个双向队列中,每个node结点都记录了waitStatus,并且整个AQS记录了一个state的状态位。
这里我们使用非公平锁进行代码的追踪
利用CAS先进行比较,我们期待的值为0,希望更新的值为1,如果status为0说明没人使用这把锁,如果status为1说明已经有人使用这把锁,那么cas将会失败,则会走acquire(1)方法
第一个tryAcquire(arg)方法
子类必须实现此模板方法,否则会抛出异常,定义规范,钩子函数
实现类如下:
这里我们是使用非公平锁,所以选择第二个实现类,点进去之后如下:
大概逻辑就是先获取aqs中的state的值,如果为0说明锁没有被占用,该线程可以获取,如果不为0说明锁已经被其他线程占用了,但是有可能是持有该锁的线程重新获取这把锁,所以就是可重入锁的判断,即else-if内部代码逻辑。
所以由于之前添加了线程a拿到了锁,所以线程b无法获取,那么tryAcquire(arg)方法返回false,那么!tryAcquire(arg)就是true,就会执行后面判断中的acquireQueued(addWaiter(Node.EXCLUSIVE),arg))方法,然后就是看addWaiter(Node.EXCLUSIVE)方法:EXCLUSIVE代表独占式的。
大概逻辑就是将线程封装成结点入队的操作
Node结点最重要的几个属性就是waitStauts,prev,next,head,tail,thread属性
由于前面线程中还没有进入过队列,所以一开始tail为空,if语句就不成立了,会进入enq(node)方法:
前面说了一开始队列为空时,tail为空,会进入到if(t==null)语句内部对队列进行初始化操作,就是创建一个空结点作为哨兵结点,让头节点的tail和head指向该空结点。然后for是死循环,此时if语句是不成立,会进入到else语句,那么就会将前面封装好的线程B结点插入到哨兵结点的后面返回。
后面的C,D线程获取锁就不会走enq(node)方法了,因为此时队列里面有两个结点分别为哨兵结点和线程B结点,所以addWaiter(mode)方法中的tail是不为空的,会进入到if语句代码块中,会将C,D线程结点分别插入到队列中去返回。
addWaiter的返回值就作为acquireQueued方法中node的实参值,node为新添加线程结点的值,node.predecessor()方法是获取node结点的前一个结点的值,因为线程b结点前一个是哨兵结点,所以这里p是一个哨兵结点,所以p==head成立,然后还有一个tryAcquire(arg)方法的判断,这里当然获取锁失败,因为锁已经被线程a占用了,所以整个if语句返回false。然后会来到shouldParkAfterFailedAcquire(p, node)方法的判断:
这里的判断逻辑就是看看node前一个结点的waitStatus的值是否为Node.SIGNAL==-1或者大于0,如果都不是则会执行compareAndSetWaitStatus
方法,设为Node.SIGNAL==-1,最后shouldParkAfterFailedAcquire方法返回false,不会执行后面的parkAndCheckInterrupt方法,然后自旋后,再次进入到shouldParkAfterFailedAcquire方法后ws为-1最后返回true。所以说该方法执行了两次。
返回true之后就会执行if判断语句后面的parkAndCheckInterrupt()方法,这里就会用到我们前面学到的LockSupport.park(object)线程阻塞方法,注意此时线程b就会被阻塞等待唤醒,即线程b会一直卡在parkAndCheckInterrupt()方法中等待着unpark()将其唤醒。此时线程b才是真真正正被阻塞在队列里面。
if(shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){}
标签:node,Node,结点,AQS,队列,源码,线程,方法,详解
From: https://www.cnblogs.com/sunshineTv/p/17460842.html