这里以
ReentrantLock
为切入点分析AQS
实现多个线程上锁以及如何阻塞等待的分析
ReentrantLock
中实现公平锁和非公平锁是用两个类实现的:FairSync
和NonfairSync
AQS
指的是AbstractQueuedSynchronizer
,ReentrantLock
里面的抽象静态内部类Sync
继承自AQS
,而ReentrantLock
实现公平锁和非公平锁的两个类则是继承自Sync
:
1.AQS同步队列结构
-
-
这里面的
Node
是AQS
的一个内部类,每个Node
表示一个在同步队列中等待抢锁的线程,他们一起形成了一个双端队列,每个Node
都有prev
何next
属性指向前后Node
:-
注意:该队列第一个是虚拟结点,从第二个结点开始才是代表不同的线程
-
队列为空的时候是这么设置的:
if (compareAndSetHead(new Node())) tail = head;//头尾指针都指向虚拟头结点
- new一个空结点当头结点,头尾指针都指向虚拟头结点
- 头尾指针相同的情况就是此时这样只有一个虚拟头结点,或者队列未初始化,都是null
-
-
即抢锁未成功的线程需要进入该队列阻塞等待
-
-
2.ReentrantLock
上锁方法lock()
-
公平锁:
final void lock() { //直接尝试获得,这个1表示让锁状态计数器+1,这个状态是0表示锁没人占用,>0表示有人使用,一般一个线程用lock()方法获得锁让该计数器从0变成1,unlock()一次就-1,变成0就表示锁没人占用。但是ReentrantLock是可重入锁,一个线程如果在释放锁前再次进入,就会让该计数器从1加到2,这样他就需要用unlock()释放两次锁 acquire(1); }
-
非公平锁
final void lock() { //非公平锁直接尝试抢夺锁,compareAndSetState(0, 1)表示如果锁的状态计数器是0,则将它变成1,即该锁进入被占用状态 if (compareAndSetState(0, 1)) //如果修改锁状态成功,将当前线程设置成锁的拥有者 setExclusiveOwnerThread(Thread.currentThread()); else //如果抢夺失败,和公平锁一样进入acquire() acquire(1); }
acquire()
方法
//arg就是上面传入的1
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 该方法是
AQS
类中的方法,final
修饰让子类无法覆盖,它包含三个关键方法:tryAcquire()
,acquireQueued()
,addWaiter()
- 其中
tryAcquire()
需要子类进行重写,另外两个不需要,这里使用的是设计模式中的模板设计模式
- 其中
acquireQueued()
方法返回的结果是当前线程是否被中断,中断表示一个状态表示,如果是,这里进入到selfInterrupt()
里面再次将当前线程中断,因为里面返回是否中断的时候用的是静态的Thread.intrrupt()
,这个方法除了返回中断标识,还会清空中断状态。- 我认为用这样是为了让线程在
acquireQueued()
里继续运行完,因此将中断标识位清空,将结果返回,最后在这里自己主动中断。
- 我认为用这样是为了让线程在
2.1 tryAcquire()
方法
-
该方法需要子类重写,因此公平锁和非公平锁实现不一样,但是只有一个地方不同:
-
公平锁:
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; }
-
非公平锁:
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); //得到当前锁的状态 int c = getState(); //如果c==0表示锁没人占用 if (c == 0) { //如果当前锁状态是0,就替换成acquires,抢到该锁 if (compareAndSetState(0, acquires)) { //然后将该锁的拥有线程设置成自己,然后返回true setExclusiveOwnerThread(current); return true; } } //如果c!=0表示当前锁被占用,检查是否是自己占用的,是的话就让锁的状态计数器+1,表示重入一次 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } //都不行就返回false,占用锁失败 return false; }
-
-
可以看到两个方法的唯一区别是这条判断逻辑公平锁多了
!hasQueuedPredecessors()
hasQueuedPredecessors()
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
-
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()); }
t
指的是同步队列的尾结点,h
指的是头结点h!=t
:如果两者相同,则直接返回false
,外层获取它的结果的时候有取反!hasQueuedPredecessors()
,因此最后是ture
,表示可以争夺锁- 因为头尾指针相同的话表示:
- 队列还未初始化,此时
h==t==null
,此时队列中没有其他节点,可以争夺锁 - 队列已经初始化,但是只有一个虚拟头结点(第一节),此时
h==t
,可以争夺锁
- 队列还未初始化,此时
- 因为头尾指针相同的话表示:
(s = h.next) == null || s.thread != Thread.currentThread()
,如果h.next==null
表示当前前队列只有除了虚拟头结点没有其他结点,此时h==t
,不会走到这里,因此走到这里判断的时候h.next
一定不为空- 然后
s.thread != Thread.currentThread()
判断当前线程是否是第一个线程h.next
(h是虚拟头结点,真正的线程结点是第二个h.next),如果相等,返回false
,经过外层取反是true
,表示可以争夺锁
2.2 addWaiter()
方法
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属性指向尾结点
node.prev = pred;
//将尾结点设置成当前结点
if (compareAndSetTail(pred, node)) {
//将pred指向的之前的尾结点的next属性指向当前结点
pred.next = node;
return node;//返回,因此一般不会进入下面的enq(node)
}
}
//一般是上面pred==null的时候进入,表示队列未初始化
enq(node);
return node;
}
-
enq(node)
private Node enq(final Node node) { for (;;) {//死循环 Node t = tail; //尾结点为空,表示需要初始化 if (t == null) { // Must initialize //将空的Node设置成头结点head if (compareAndSetHead(new Node())) tail = head;//将head赋给tail } else { //第一轮循环初始化队列,第二轮进来这里 node.prev = t;//记录此时尾结点是哪一个 //将当前node设置成新的尾结点 if (compareAndSetTail(t, node)) { //将之前的尾结点的next指向node t.next = node; return t;//退出死循环 } } } }
-
可以看到
enq()
是用来初始化队列的,它是死循环,第一轮循环初始化队列,设置虚拟头结点,第二轮循环就是把当前结点入队,这部分代码其实和进入enq()
之前判断已经初始化后执行的是一样的-
//如果队列已经初始化,则不会进入enq if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //=========================== //enq中初始化队列后 else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } }
-
-
2.3 acquireQueued()
方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;//表示是否取消排队
try {
boolean interrupted = false;//是否被中断
for (;;) {//死循环,除非取消排队或者抢到了锁,不会退出?
final Node p = node.predecessor();//得到当前结点的前驱结点
//如果p是头结点,就又用tryAcquire尝试抢锁,因为此时当前结点是第一个线程,无论公平还是非公平自己都能抢夺
if (p == head && tryAcquire(arg)) {
//抢锁成功,需要将结点出队,这里采用的方法是将当前结点当做下一轮的虚拟头结点,让原本的头结点p置空被GC回收,代码在底下
setHead(node);
p.next = null; // help GC
failed = false;//不取消排队
return interrupted;//未被中断false
}
//shouldParkAfterFailedAcquire方法下面单独分析
if (shouldParkAfterFailedAcquire(p, node) &&
//前面返回true表示当前线程进入等待资源释放状态,因此需要阻塞,该方法直接将线程阻塞,代码在底下
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)//最后看是否想取消排队
cancelAcquire(node);
}
}
//===================================
//setHead方法
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
//parkAndCheckInterrupt方法
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//直接阻塞当前线程,用的是通行证阻塞
return Thread.interrupted();
}
-
- 可以看到死循环中只有一个出口就是
return
,也就是说没抢到锁就不会退出,但也不一定一直循环抢,后面会看当前线程的等待状态判断是否阻塞,阻塞后就等待其他抢到锁的线程退出的时候需要取消排队,cancelAcquire(node)
就有唤醒机制,抢到后将排队意愿设置成取消,后面的方法会将当前线程对应的结点从同步队列中移除(就像链表移除结点,将前驱或后继连接)
- 可以看到死循环中只有一个出口就是
-
注意:
parkAndCheckInterrupt()
方法结束阻塞后调用静态方法判断当前线程是否被中断,静态方法会清楚当前中断标志位,因此无论是否被终端,返回到上一层 -
shouldParkAfterFailedAcquire()
-
- 可以看到每次都是将自己的前驱结点设置成**等待被占用资源释放**状态,即
SIGNAL
- 可以看到每次都是将自己的前驱结点设置成**等待被占用资源释放**状态,即
-
2.4 cancelAcquire()方法
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;//将当前结点表示的线程置空
// Skip cancelled predecessors
Node pred = node.prev;//得到当前结点前驱
//如果前驱也取消了,就像自己的左结点指向前驱的前驱,一直到找到未取消的前驱
while (pred.waitStatus > 0)//如果前驱状态为>0(只有处于取消状态的状态为是大于0的),就跳过,找前驱的前驱
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;//当前结点状态置为取消状态
// If we are the tail, remove ourselves.
//如果当前节点是尾结点,就将前驱结点设置成新的尾结点,并且将pred的next置空
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
//总之就是如果前驱结点不是头结点,并且里面线程不为空,就将前驱结点的next指向当前结点的next
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;//拿出当前结点的next
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {//否则唤醒当前结点的后继结点,代码在下面
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
-
unparkSuccessor()
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); }
3.ReentrantLock
解锁方法unlock()
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {//锁释放成功返回ture,代码在下面
Node h = head;
if (h != null && h.waitStatus != 0)
//由于锁释放了,唤醒下一个结点让他从上面的阻塞醒来,争夺锁
unparkSuccessor(h);
return true;
}
return false;//没有成功释放锁
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//将锁状态计数器-1,如果没有重入的话c是1,此时-1等于0,表示锁释放了
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);//将锁的占用现成置为空
}
setState(c);//将锁状态更新
return free;
}