1.原理交代
- state:表示重入锁的重入次数,如果为0是表示锁无人获得。
- head:直线头结点,默认情况下他的后继结点无值。此时头结点和尾结点均指向此处。
- tail:指向尾结点,及最后一个结点,初始情况下和head指向同一个首结点。
- exclusiveOwnerThread:存放当前后的锁的线程。
- 当state=0 && exclusiveOwnerThread = null时可以抢占次锁。 当锁闲置是有新线程过来抢锁时,无须进入队列,可以和第一个结点一并抢占锁。
2.测试代码,注意真正执行的时候是高并发进来,咱们抽丝剥茧,进入时间内部看看它到底怎么执行的。
package com.yuhl.concurrent.AQS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author yuhl
* @Date 2020/10/18 17:26
* @Classname Fair001
* @Description AQS非公平锁的源码解读
*/
public class Fair001 {
public static void main(String[] args) {
//构建公平锁。如果无参默认为非公平锁哦!
ReentrantLock lock = new ReentrantLock(true);
try {
lock.lock();//加锁
TimeUnit.SECONDS.sleep(1);//次任务执行1
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
}
3.当第一个并发线程T1进来时的执行过程:
当代码执行到
lock.lock();//加锁
这句的时候发生了一系列的故事。我们进入其中一探究竟吧:
public void lock() {
sync.lock();
}
调用:FairSync.sync.lock():,特别注意,现在讨论的是公平锁:
final void lock() {
acquire(1);
}
lock,调用acquire(1);方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire(int arg)方法传入的参数为1,这个1就是要更新的state中的。
此处有三个方法,我们先看第1个tryAcquire(arg)方法(试着去获取锁),我们是第一个线程T1进来的,所有他肯定拿得到锁的哦,我们详细看下他的源码吧:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();//第一次进来,state默认为0,没线程占用。
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
第一个线程进来,state默认为0,没线程占用,所以走上面的if方法,其中if中有中两个方法我们
- 先看第一个hasQueuedPredecessors() :
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
此时头节点h,尾节点s均为null,及初始化了头尾节点。由于此时队列为空故返回false。
- 此时开始执行第二个方法compareAndSetState(0, acquires):
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
调用底层的compareAndSwapInt方法:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
设置state=1。然后进入if代码块中执行setExclusiveOwnerThread(current)方法,这只exclusiveOwnerThread = currentThread。至此占坑成功。
4.此时T1占坑中因为T1需要占坑1秒钟,这可是很长的时间啊,此时在0.2秒T2进来了:一下前几部和前面T1进来时一模一样哦。
执行lock方法当代码执行到
lock.lock();//加锁
这句的时候发生了一系列的故事。我们进入其中一探究竟吧:
public void lock() {
sync.lock();
}
调用:F在这里插入代码片airSync.sync.lock():,特别注意,现在讨论的是公平锁lock():
final void lock() {
acquire(1);
}
然后 acquire(1);方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
到此和T1的调用是一致的,现在开始
- 调用tryAcquire(arg)方法:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
此时的state为1,已经被占坑了。current != getExclusiveOwnerThread(),因为current=T2,而exclusiveOwnerThread=T1所以else if的条件也不满足所以,代码往下走返回false。
- acquire()方法中的acquireQueued(addWaiter(Node.EXCLUSIVE), arg)中的addWaiter(Node.EXCLUSIVE)方法:
插入说明:Node里面两个对象,下一个Node,和当前的Thread
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
回归正题开始调用addWaiter(Node.EXCLUSIVE)方法,此时尾节点是null(因为AQS中只有头和尾):
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;//T2的前置指向head
if (compareAndSetTail(pred, node)) {
pred.next = node;//head的后置指向T2
return node;
}
}
enq(node);
return node;
}
即对象pred 是null,不走if,所以此时直接调用enq(node);方法:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;//让未节点也指向这个属性为null的new Node()对象
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- for的第一次死循环:让未节和头节点均指向这个属性为null的new Node()对象。
在这个死循环里面,先调用了compareAndSetHead(new Node())方法,给head节点设置值,之间头和尾均为null,看compareAndSetHead(new Node())代码如下:
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
调用链native到底了了:
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
截图说明如下:
tail = head;//让未节点也指向这个属性为null的new Node()对象
- for的第二次死循环:
走的是sles这个逻辑:
此时这个头节点和T2对应的节点构建好了。
AQS的情况如下:
返回了头节点(这个分会没哟意义,因为调用它的方法没有接收这个返回值),跳出了for循环。
addWaiter()方法到此接收,做了一件把新进入的T2放入了链表中。 - 开始调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) = acquireQueued(T2, arg)这个方法,主要作用是park住当前的T2线程源码如下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {//他的前置就是p,而且他重新去 tryAcquire(arg)返回依然是false,因为此时是0.2s, T1那个哥们还没有释放锁呢。
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
调用shouldParkAfterFailedAcquire()方法进行T2线程的park操作,源码如下:
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 must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
此处插入一个waitStatus 的状态解释:
| waitStatus | 解释 |
|---|---|
| 1:CANCELLED | 在同步队列中等待的线程等待超时或者被中断,取消继续等待 |
| 0 | 初始化状态 |
| -1 :SIGNAL | 当前结点表示的线程在释放锁后需要唤醒后续节点的线程 |
| -2:CONDITION | 等待条件状态,在等待队列中 |
| -3:PROPAGATE | 状态需要向后传播 |
插入操作结束,回到正题上。
ws为头节点的waitStatus = 0 默认值,所以走compareAndSetWaitStatus(pred, ws, Node.SIGNAL);方法,源码如下:
/**
* CAS waitStatus field of a node.
*/
private static final boolean compareAndSetWaitStatus(Node node,
int expect,
int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset,
expect, update);
}
结果如下:设置头结点的waitSattus=-1,意味着,头结点,下次就要唤醒其后节点了。
此时回到final boolean acquireQueued(final Node node, int arg) 方法开始第二次for循环,第一次for循环,没有park住。看第二次的情况,private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) 中执行下面代码:
```java
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
及此返回true。 返回上层方法开始执行parkAndCheckInterrupt()方法,开始去park:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//T2线程在这个里park住了,
return Thread.interrupted();
}
此处插入一个点1:
//这两个都是对于threadOne来说的
//设置中断标志
threadOne.interrupt();
//获取中断标志
System.out.println("isInterrupted:" + threadOne.isInterrupted()); //true
此处插入一个点2:
//对当前main线程进行终端表示的设置
Thread.currentThread().interrupt();
//获取中断标志并重置
System.out.println("isInterrupted--main:" + threadOne.interrupted()); //true
原因为:interrupted()方法获取的终端的线程的中断标识,不管是threadOne.interrupted()调用的,还是threadTwo.interrupted()调用的,因为,看他的源码,从源码可以看到,调用的是currentThread()当前线程的中断标识,并不取决于那个实例调用的:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
插入只是结束。
回归正题:
park进入下面方法开会时park主:
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);//这行代码让他park住了。
setBlocker(t, null);
}
UNSAFE.park(false, 0L);//这行代码让他park住了。
总结上面的步骤:
1.T1在AQS中抢到了锁,这个锁需要1才能释放,
2.在0.2秒的时候T2来了。此时会发生什么事呢:先初始化头和尾几点为field为null的Node节点;然后构建T2的Node链表;然后和现有的T2Node节点形成一个双向链表;然后去Park住。
5.在0.3秒的时候又来了个线程T3,此时会发生什么事情能?
整个流程和T2一模一样也挂起了。此时请款如下:
6.在1秒的时候又来了个线程T4,此时会发生什么事情能?注意此时,T1也结束了。即此时T1调用:
lock.unlock();
需要唤醒Queue中第一个node1即T2,同时,还有一个T4要和他争抢这把锁.
花开两朵,各表一支,
- 我们先看T2的情况
public void unlock() {
sync.release(1);
}
调用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;
}
调用 unparkSuccessor(h);方法,h表示的是head节点:
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)//头节点当然不为空了。可以从文章上面的图看出来。
LockSupport.unpark(s.thread);
}
走最后一行代码s=node.next,说明s是T2:
if (s != null)//头节点当然不为空了。可以从文章上面的图看出来。
LockSupport.unpark(s.thread);
解锁成功:
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
T2解锁。
接下来T2解锁后怎么执行吗?我们回到当时T2挂起的地方,接着执行就可以了这个就是T2挂起的地方:
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);//挂起的代码,
setBlocker(t, null);
}
执行
setBlocker(t, null);
然后执行到调用它的地方继续执行:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
执行return Thread.interrupted();由于在当前线程中认为设置 T2.interrupt();所以Thread.interrupted();返回为false。
插入一个小小的解释:
T2.interrupted(); 如果有认为的 T2.interrupt(),此时T2.interrupted()他会获中断标识,返回为true,如果没有人(程序员)设置,则直接返回的就是false。
插入小知识结束。
继续回调用次方法的地方:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
parkAndCheckInterrupt()返回为false,此时继续下一次for循环,先看第一个if(p == head)中p == head为true,所以会执行关键方法tryAcquire(arg),试着去获取锁,此时花开两朵,各表一支的前一只T2到此接收,他要执行
tryAcquire(arg),
- 但是另一只T4也通过调用:
lock.lock();//加锁
也同时到达了tryAcquire(arg)这个方法,此时T2和T4就需要真强这把锁了。
调用:
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
调用:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
看到这个compareAndSwap()就放心了。T2和T4他肯定有一个能抢到。
- 如果T4抢到则T4执行,AQS链表不变。
- 如果T2签到,则链表发生该变head->T3->T4.(T4需要乖乖的回去排队了)
到此整个公平锁的AQS机制介绍完毕。