前言
對源碼
AQS(AbstractQueuedSynchronizer)
(在java.util.concurrent.locks
)的分析會是一個系列文章.這篇文章主要是以例子的形式并且一步步把源碼中的代碼加入我們自己的代碼中,從而一步步完成對AQS
的源碼分析.
完整代碼:代碼
添加必要的接口和抽象類
Lock
public interface Lock {
// 獲取鎖
void lock();
// 獲取鎖 響應(yīng)中斷
void lockInterruptibly() throws InterruptedException;
// 獲取鎖 如果鎖是available立即返回true, 如果鎖不存在就立即返回false
boolean tryLock();
// 在上面的基礎(chǔ)上加一個時間限制
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 釋放鎖
void unlock();
// 后續(xù)博客會討論
Condition newCondition();
}
定義了鎖的一些常規(guī)操作
AbstractOwnableSynchronizer
定義了一個獨占鎖當前所對應(yīng)的線程并且提供了
set
和get
方法
public class AbstractOwnableSynchronizer implements java.io.Serializable {
private static final long serialVersionUID = 3737899427754241961L;
protected AbstractOwnableSynchronizer(){}
//獨占鎖對應(yīng)的線程
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
this.exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return this.exclusiveOwnerThread;
}
}
實現(xiàn)AbstractOwnableSynchronizer(以下以AQS簡稱)
在介紹主體方法之前先介紹一個內(nèi)部類
Node
,AQS中等待隊列的元素就是Node
類的一個個對象.
Node
static final class Node {
// 共享
static final Node SHARED = new Node();
// 表示獨占
static final Node EXCLUSIVE = null;
// 表明這個節(jié)點已經(jīng)被取消
static final int CANCELLED = 1;
// 表明需要喚醒下一個等待的線程
static final int SIGNAL = -1;
// 后續(xù)會討論
static final int CONDITION = -2;
// 后續(xù)會討論
static final int PROPAGATE = -3;
// 當前節(jié)點的等待狀態(tài)
volatile int waitStatus;
// 當前節(jié)點的前一個節(jié)點
volatile Node prev;
// 當前節(jié)點的下一個節(jié)點
volatile Node next;
// 當前節(jié)點所表示的線程
volatile Thread thread;
// 用于判斷是否是獨占還是共享
Node nextWaiter;
//判斷是獨占還是共享
final boolean isShared() {
return nextWaiter == SHARED;
}
// 取前一個節(jié)點
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
// 無參構(gòu)造函數(shù)
Node() {}
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
public String toString() {
return "[" + (thread == null ? "NULL" : thread.getName()) + "," + waitStatus + "]";
}
}
Node
類是一個封裝了thread
并且?guī)в幸恍顟B(tài)的節(jié)點
1. 所以它有個thread
屬性表示該節(jié)點所對應(yīng)的線程.
2. 還有屬性nextWaiter
來表示該節(jié)點是共享鎖SHARED
的還是獨占鎖EXCLUSIVE
的
3.toString()
函數(shù)是為了方便打印我自己加上的
4. 有一個waitStatus
來表示當前節(jié)點的等待狀態(tài),有(CANCELLED
,SIGNAL
,CONDITION
,PROPAGATE
)
CANCELLED
: 表示該節(jié)點已經(jīng)被取消了
SIGNAL
: 表示該節(jié)點在release
或者取消時需要去喚醒下一個線程(這里留個疑問?還有在什么情況下會去喚醒下一個節(jié)點的線程)
后面的兩個屬性會在后面的系列博客中有分析到
其實還有一個屬性是0
在生成節(jié)點的時候waitStatus
默認是0
AQS的屬性
public class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private static final long serialVersionUID = 7373984972572414691L;
protected AbstractQueuedSynchronizer() { }
static final class Node {}
// 同步器等待隊列的頭節(jié)點
private transient volatile Node head;
// 同步器等待隊列的尾節(jié)點
private transient volatile Node tail;
// 同步器的狀態(tài)值
private volatile int state;
// 獲取同步器狀態(tài)值
protected final int getState() {
return state;
}
// 設(shè)置同步器的狀態(tài)值
protected final void setState(int newState) {
state = newState;
}
public final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
private static final Unsafe unsafe;
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe)f.get(null);
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
private static final boolean compareAndSetWaitStatus(Node node,
int expect,
int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset,
expect, update);
}
private static final boolean compareAndSetNext(Node node,
Node expect,
Node update) {
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
先說幾個屬性
1.head
和tail
表示等待隊列的頭尾節(jié)點
2.state
表示AQS的狀態(tài)值,在后面的例子中可以更清晰的理解它的作用. 并且提供了set
和get
方法.
然后說一下跟源碼的區(qū)別
源碼:private static final Unsafe unsafe = Unsafe.getUnsafe();
由于在我們的代碼使用這個語句會報錯,因為在類加載的時候必須要使用對應(yīng)的類加載器,然而我們可以使用反射的方式拿到該對象.
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe)f.get(null);
Unsafe
的相關(guān)說明會寫一個博客來另外分析,現(xiàn)在只需要知道它在多線程情況下是可以安全實現(xiàn)那個對應(yīng)的操作就可以了
接下來我們使用一個例子來看看如何一步步實現(xiàn)一個互斥鎖的.
例子Mutex互斥鎖
既然我們是要實現(xiàn)一個鎖,那我們可以繼承
Lock
接口,然后我們使用AQS的一個子類的一個對象來真正實現(xiàn)Lock
的功能.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
public class Mutex implements Lock {
private final Sync sync = new Sync();
private static class Sync extends AbstractQueuedSynchronizer {
protected boolean isHeldExclusively() {
return getState() == 1;
}
public boolean tryAcquire(int acquires) {
if (super.compareAndSetState(0, 1)) {
super.setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
}
public void printWaitingNode() {
sync.printQueue();
}
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
// TODO Auto-generated method stub
}
@Override
public boolean tryLock() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
// TODO Auto-generated method stub
return false;
}
@Override
public void unlock() {
// TODO Auto-generated method stub
}
@Override
public Condition newCondition() {
// TODO Auto-generated method stub
return null;
}
}1
從上面代碼中可以看到,AQS中的狀態(tài)是0時表明鎖是空閑的,1表明鎖已經(jīng)被某個線程獨占了,表明這個線程已經(jīng)擁有了這個鎖,那根據(jù)我們的猜想,其余沒有獲得鎖的線程放到哪里呢?沒錯,就是放到等待隊列中,那接下來我們先看看用一個測試類看看效果.
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
Mutex m = new Mutex();
for (int i = 0; i < 5; i++) {
new Thread(new Runner(m), "thread-" + i).start();;
}
for (int i = 0; i < 5; i++) {
m.printWaitingNode();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Runner implements Runnable {
Mutex m;
public Runner(Mutex m) {
this.m = m;
}
@Override
public void run() {
m.lock();
System.out.println(Thread.currentThread().getName() + " runs");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
m.unlock();
}
}
}
運行結(jié)果如下:
表明thread-0
獲得了鎖,其余的線程在等待隊列中.
thread-0 runs
[NULL,-1]->[thread-1,-1]->[thread-2,-1]->[thread-3,-1]->[thread-4,0]->
那接下來看看如何一步步實現(xiàn)
AQS
中的acquire
方法的.建議看下面分析是可以帶著Mutex
的代碼來看下面的分析
acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
由于調(diào)用了
tryAcquire
和acquireQueued
方法,我們先看看這兩個方法再回頭來分析這個方法
tryAcquire方法
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
作用: 獲得了鎖返回true,沒有獲得返回false
這個方法是為子類準備的,所以在Mutex
也實現(xiàn)了tryAcquire
方法,所以第一個線程thread-0
獲得鎖的調(diào)用過程中,tryAcquire
已經(jīng)返回true
,所以根本不會去執(zhí)行acquireQueued
方法和selfInterrupt
所以當該線程沒有獲得鎖的時候就要執(zhí)行
acquireQueued
方法,可以大膽預(yù)測該方法是想讓這個線程加入到等待隊列中并且讓它阻塞(其實仔細一想,獲得了鎖的線程其實是程序沒有去阻塞它而已嘛,讓它繼續(xù)執(zhí)行罷了,那沒有獲得鎖的線程就得阻塞它,讓它沒有辦法繼續(xù)執(zhí)行)
在看
acquireQueued
方法前,先看看addWaiter
方法
addWaiter(Node.EXCLUSIVE)
注意添加的參數(shù)是獨占型的節(jié)點
//添加一個節(jié)點到當前隊列 在尾部添加
private Node addWaiter(Node mode) {
// 生成一個mode類型的節(jié)點 節(jié)點的thread是當前線程
Node node = new Node(Thread.currentThread(), mode);
/**
* 如果等待隊列尾部不為空,則直接通過unsafe操作放到隊列尾部
* 如果不是則調(diào)用 enq方法把節(jié)點插入到等待隊列中.
*/
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
作用:將當前thread包裝成節(jié)點,獨占型,waitStatus=0
流程分四步:
1. 生成node
節(jié)點[獨占型,waitStatus=0,thread=Thread.currentThread()]
2. 如果等待隊列尾部存在,表明鏈表已經(jīng)初始化過了,可以直接放到鏈表尾部
3. 如果鏈表沒有初始化,那就是讓enq(node)
去初始化鏈表并且把node
加入到鏈表尾部
4. 返回這個node
接下來看看
enq
方法
enq(final Node node)方法
參數(shù)node是即將要放入到隊列尾部的節(jié)點
// 將node節(jié)點放到等待隊列的尾部
private Node enq(final Node node) {
/**
* 無限循環(huán)到條件滿足后退出
*/
for (;;) {
Node t = tail;
/**
* 如果隊列尾部是空 表明隊列還沒有進行過初始化
* 如果不為空 則把節(jié)點鏈接到隊列的尾部
*/
if (t == null) {
//生成一個thread為null的節(jié)點并且設(shè)置為頭節(jié)點
if (compareAndSetHead(new Node()))
tail = head;
} else {
//利用unsafe的操作把當前節(jié)點設(shè)置到尾部
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
作用:將node節(jié)點放到等待隊列的尾部
流程:通過for循環(huán)自旋的方式一直檢查尾部,如果尾部為空,則創(chuàng)建一個thread為null
的頭節(jié)點,然后將其設(shè)置為尾節(jié)點,如果不為空,則把該節(jié)點加入到隊列尾部.
問題:至于為什么需要用for循環(huán)自旋的方式呢?1. 當鏈表是空的時候,只有一個線程進來的時候會先進入
if(t==null)
塊中,完成后會再次進入for循環(huán)中的else
塊中并返回.
2. 當有兩個線程同時進入else
塊中的時候,只能有一個線程會設(shè)置成功,所以失敗的那個線程會進入for循環(huán)后再次進入else
塊中.
到這里我們先簡單總結(jié)一下:
1. 該線程由于在
tryAcquire
方法中返回false
表明沒有獲得鎖,因此需要被阻塞
2. 我們已經(jīng)通過addWaiter
方法把該線程包裝成一個獨占型的節(jié)點放入到等待隊列的尾部并且返回了該節(jié)點.
那按照思路接下來的操作就應(yīng)該是如何來阻塞這個線程了,那接下來我們來看看這里最核心的
acquireQueued
方法
acquireQueued(final Node node, int arg)方法
/**
*
* @param node 當前節(jié)點
* @param arg acquire請求的狀態(tài)參數(shù)
* @return 返回是否需要對當前運行線程進行中斷
*/
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);
}
}
這里有幾個新的方法,我先簡單介紹一下每個方法的作用,具體的細節(jié)分析會放到后面的分析中.
1.predecessor()
取得當前節(jié)點node的前驅(qū)節(jié)點 屬于Node
中的成員方法
2.setHead(node)
把當前節(jié)點node設(shè)置為頭節(jié)點
3.shouldParkAfterFailedAcquire(p, node)
返回當前節(jié)點node是否可以進行休眠
4.parkAndCheckInterrupt()
讓當前線程進行休眠狀態(tài)并且在喚醒或者中斷后返回中斷狀態(tài)
5.cancelAcquire(node)
取消當前節(jié)點node
關(guān)于兩個屬性也簡單介紹一下
1.failed
表示獲得鎖是否成功
2.interrupted
表示當前節(jié)點對應(yīng)的線程是否有被中斷過
作用:利用for循環(huán)自旋的方式讓等待獲取鎖的線程都進行休眠,因為休眠可以節(jié)約cpu資源,讓擁有了鎖的線程可以充分利用cpu運行
流程如下:
所以我們接下來就看看上面那三個方法的具體實現(xiàn)
setHead(node)方法
/**
* 作用: 把node節(jié)點設(shè)置為等待隊列的頭節(jié)點
*/
private void setHead(Node node) {
/**
* 1. 把當前節(jié)點設(shè)置為頭節(jié)點
* 2. 把當前節(jié)點的thread設(shè)置為null,因為這個node.thread已經(jīng)獲得了鎖
* 3. 把當前節(jié)點的前鏈接設(shè)置為null
*/
head = node;
node.thread = null;
node.prev = null;
}
作用: 把節(jié)點設(shè)置為等待隊列的頭節(jié)點
注意:waitStatus
的值沒有修改
shouldParkAfterFailedAcquire(Node pred, Node node)方法
/**
* 這個方法必須要保證pred == node.prev
* 作用: 此方法是在判斷node節(jié)點是否可以進行休眠狀態(tài), 因為進行休眠狀態(tài)可以節(jié)約資源利用(cpu)
* 所以需要讓node休眠前要確保有節(jié)點可以喚醒它,因此下面所做的事情就是檢查等待隊列中
* 前面的節(jié)點是否滿足條件.
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/**
* 如果節(jié)點pred的狀態(tài)是Node.SIGNAL的時候,表明pred在release的時候
* 會喚醒下一個節(jié)點,也就是node節(jié)點
* 返回true 表明node節(jié)點可以休眠
*/
return true;
if (ws > 0) {
/**
* 表明節(jié)點pred所對應(yīng)的線程已經(jīng)被取消了,因此需要一直往前找
* 直到前一個節(jié)點的waitStatus小于等于0,中間的那些取消的節(jié)點
* 會被刪除 因為最后pred.next = node.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/**
* 所以這個分支表示當前節(jié)點的等待狀態(tài)要么是0或者PROPAGATE,
* 此時直接把它設(shè)置為SIGNAL
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
作用:是判斷
node
節(jié)點是否可以進行休眠狀態(tài),但是休眠前需要保證有節(jié)點可以喚醒它,這個時候Node.SINGAL
就是這個作用.
流程:
juc_3.png
ws>0塊所做的內(nèi)容如下:
juc_2(1).png
[疑問]:多個線程進入到這里的時候會不會有問題?歡迎大家一起討論
看完上面兩個圖然后再結(jié)合著
acquireQueued方法
看一下可以知道線程在休眠前必須要保證有前驅(qū)節(jié)點可以喚醒它,而這個標志就是使得前驅(qū)節(jié)點的waitStatus
要為Node.SIGNAL
請注意:比如pred的waitStatus不是1或者-1的時候,當?shù)谝淮握{(diào)用該方法時會返回false,第二次調(diào)用會直接返回true,因為返回到
acquireQueued
中的for
循環(huán)中還是有可能會繼續(xù)調(diào)用該方法.(waitStatus是1的時候大家可以自行判斷一下)
那到這里我們已經(jīng)知道當線程沒有取得鎖的時候,已經(jīng)有個方法會告訴當前線程是否可以進行休眠,并且在沒有獲得鎖的情況下盡量會讓該線程休眠,所以接下來我們看看如何讓當前線程休眠的
parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
作用: 讓當前線程進行休眠狀態(tài)并且在喚醒或者中斷后返回中斷狀態(tài)
注意: 第一句就會讓當前線程進行休眠狀態(tài),只有等到當前線程被喚醒后才可以進入第二句話
線程被喚醒有兩種情況:
1. 被前面的(SIGNAL)節(jié)點喚醒 此時第二句話返回false
2. 線程被中斷 此時第二句話返回true (關(guān)于中斷的話題會有另外專門博客介紹)
其實到這里我們就可以看明白我們前面那個例子最后打印出來的等待隊列是如何產(chǎn)生的了.
階段性總結(jié)
分析到這里我們就可以回頭看看
acquire(int arg)
的流程圖了
juc_5(1).png
另外對上面的例子所表現(xiàn)出來的結(jié)果如下:
juc_4.png
解釋一下:
1. thread-0沒有出現(xiàn): 因為thread-0
已經(jīng)獲得了鎖在tryAcquire
中就已經(jīng)返回了,可以看流程圖
2. 頭節(jié)點的等待狀態(tài)是-1: 這是因為在鏈表加入thread-1
那個節(jié)點的時候會通過shouldParkAfterFailedAcquire
將剛剛初始化的0
狀態(tài)改變成Node.SIGNAL
狀態(tài).
3. 尾節(jié)點thread-4的等待狀態(tài)是0: 因為它就是初始化的狀態(tài),還沒有發(fā)生過改變.
接下來就是剩下的
cancelAcquire
cancelAcquire(Node node)
/**
* 添加cancelAcquire方法
* 作用: 取消該節(jié)點
*/
private void cancelAcquire(Node node) {
// 如果節(jié)點為null 直接返回
if (node == null)
return;
// 設(shè)置節(jié)點所對應(yīng)的thread為null
node.thread = null;
// 找到node的前驅(qū)節(jié)點(必須是沒有被取消的節(jié)點)
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 前驅(qū)節(jié)點的next節(jié)點
Node predNext = pred.next;
// 設(shè)置當前節(jié)點為取消狀態(tài)
node.waitStatus = Node.CANCELLED;
/**
* 如果當前節(jié)點是尾節(jié)點: (表明當前節(jié)點可以直接取消,不用管后面的節(jié)點,因為后面沒有節(jié)點了)
* 1. 設(shè)置前驅(qū)節(jié)點pred成為新的尾節(jié)點
* 2. 在1成功的條件下設(shè)置尾節(jié)點的next為null
*/
if (node == tail && compareAndSetTail(node, pred)) {
//情況3
compareAndSetNext(pred, predNext, null);
} else {
/**
* 表明node后面還有節(jié)點,因此后面的節(jié)點會需要喚醒
* 有兩種情況:
* 1. 前驅(qū)節(jié)點不是頭節(jié)點,并且前驅(qū)節(jié)點的等待狀態(tài)是SINGAL或者可以成為SINGAL狀態(tài),
* 并且前驅(qū)節(jié)點的thread不能為null. 這種情況下不需要去喚醒node后面的線程,因為pred節(jié)點可以去喚醒的
* 2. 如果不符合1,就需要主動去喚醒node后面的節(jié)點線程了,因為此時不喚醒,node后面節(jié)點的線程就沒辦法再喚醒了
*/
int ws;
//情況1
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//情況2
unparkSuccessor(node);
}
//設(shè)置node節(jié)點自旋
node.next = node; // help GC
}
}
當如果有異常的時候會進入該方法中取消該節(jié)點
以下列出了三種情況分別出現(xiàn)的情景:
juc_4(3).png
注意:最后將該節(jié)點設(shè)置為自旋方便GC操作
直接看```unparkSuccessor(node)方法
unparkSuccessor(Node node)
/**
* 喚醒node的后驅(qū)節(jié)點(node后面第一個沒有被取消的節(jié)點)
*/
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);
/*
* 取得node的后繼節(jié)點
*
*/
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);
}
作用:喚醒node的后驅(qū)節(jié)點(node后面第一個沒有被取消的節(jié)點)
注意: 在尋找node的后繼節(jié)點的時候,為什么需要從后往前尋找,情況上圖中的情況2
這里有兩點疑問我還沒有弄清楚,歡迎大家一起討論
1. 開始的部分為什么需要把ws設(shè)置為0,所以我把英文直接貼上來了
接下來再看一個
selfInterrupt()
方法
selfInterrupt()方法
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
讓當前線程中斷,線程中斷的話題會寫一個專門的博客來分析的
打印等待隊列的代碼
public void printQueue() {
printQueueNode(tail);
System.out.println();
}
public void printQueueNode(Node node) {
if (node == null) return;
printQueueNode(node.prev);
System.out.print(node + "->");
}
這兩個方法是我自己為了方便打印加的,因為需要從鏈表的尾部遍歷,因此我就用了個遞歸的寫法.
至此關(guān)于獲取鎖這部分的代碼就已經(jīng)分析完了,你可以把代碼下載下來自己運行著看一下.
最后加一個鎖的釋放
鎖的釋放 release(int arg)方法
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
代碼和邏輯都比較簡單,就是當head不為null并且狀態(tài)值不為0的時候,會去喚醒下一個頭結(jié)點后的后驅(qū)節(jié)點.
請注意喚醒的線程會從parkAndCheckInterrupt中返回(此時返回值是false)到acquireQueued方法中去嘗試獲取鎖
所以接下來我們完善一下Mutex類和測試用例
完善Mutex類和測試用例
在Mutex類中unlock修改成
@Override
public void unlock() {
// TODO Auto-generated method stub
sync.release(1);
}
在Sync類中添加tryRelease方法
public boolean tryRelease(int releases) {
if (super.getState() == 0)
throw new IllegalMonitorStateException();
super.setExclusiveOwnerThread(null);
super.setState(0);
return true;
}
再次運行例子,在我的電腦上結(jié)果如下:
thread-0 runs
[NULL,-1]->[thread-1,-1]->[thread-2,-1]->[thread-3,-1]->
[NULL,-1]->[thread-1,-1]->[thread-2,-1]->[thread-3,-1]->[thread-4,0]->
thread-1 runs
[NULL,-1]->[thread-2,-1]->[thread-3,-1]->[thread-4,0]->
thread-2 runs
[NULL,-1]->[thread-3,-1]->[thread-4,0]->
thread-3 runs
[NULL,-1]->[thread-4,0]->
thread-4 runs
[疑問]: 為什么會出現(xiàn)
[NULL,-1]->[thread-1,-1]->[thread-2,-1]->[thread-3,-1]->
結(jié)尾
至此對獨占鎖的分析差不多就到這里了,歡迎大家對上面存在的疑問一起討論哈,或者哪里寫得有問題也歡迎提出一起討論哈.
后面的文章將會繼續(xù)接著本文繼續(xù)討論,對獨占鎖的中斷獲取等等,就是一步步把Mutex沒有實現(xiàn)的方法實現(xiàn)完.
完整代碼:代碼
另外如果有幫助到您一點點,請幫忙給個贊哈.謝謝觀看.
下一篇: [Java源碼][并發(fā)J.U.C]---用代碼一步步實現(xiàn)AQS(2)---獨占鎖
參考
1. Java并發(fā)編程的藝術(shù)
2. Java1.8 java.util.concurrent.locks包的源代碼