上一篇《Java鎖之ReentrantLock(一)》已經介紹了ReentrantLock的基本源碼,分析了ReentrantLock的公平鎖和非公平鎖機制,最終分析ReentrantLock仍是依託於
AbstractQueuedSynchronizer
同步隊列器(如下簡稱同步器)實現,因此本篇開始分析同步器內部的代碼實現,考慮到代碼結構比較長,因此分析源碼時會精簡部分不重要的代碼,可是最終仍是會以不影響代碼邏輯的狀況下進行精簡。node
根據上圖源碼咱們能夠知道,
AbstractQueuedSynchronizer
內部構建了一個Node節點對象,同時構造了一個具備volatile屬性頭節點與尾部節點,保證了多線程之間的可見性,同時最重要的是定義了一個int類型變量state,經過上一篇文章分析,咱們知道了ReenTrantLock是否獲取到鎖的判斷就是state是否大於0,等於0表示鎖空閒,大於0,表示鎖已經被獲取。接下來咱們重點分析下Node節點內部構造以及同步器的實現原理,Node源碼以下:bash
static final class Node {
//共享模式
static final Node SHARED = new Node();
//獨佔模式
static final Node EXCLUSIVE = null;
//取消狀態
static final int CANCELLED = 1;
//喚醒狀態
static final int SIGNAL = -1;
//等待條件狀態
static final int CONDITION = -2;
//傳播狀態
static final int PROPAGATE = -3;
//等待狀態
volatile int waitStatus;
//上一個節點
volatile Node prev;
//下一個節點
volatile Node next;
//獲取同步狀態的線程
volatile Thread thread;
//等待隊列中的後繼節點,若是當前節點是共享的,那麼這個nextWaiter=SHARED
Node nextWaiter;
//判斷當先後繼節點是不是共享的
final boolean isShared() {
return nextWaiter == SHARED;
}
//返回當前節點的前一個節點
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
複製代碼
這裏須要重點說明的屬性是
waitStatus
,該狀態就是包括節點內部聲明的幾個常量,以下:多線程
常量名 | 功能 |
---|---|
CANCELLED | 值爲1,當前節點進入取消狀態,緣由是因爲被中斷或者是等待超時而進入取消狀態,須要說明的是,節點線程進入取消狀態後,狀態不會再改變,也就不會再阻塞獲取鎖 |
SIGNAL | 值爲-1,後繼節點的線程處於等待狀態,而當前節點的線程若是釋放了同步狀態或者被取消,將會通知後繼節點,使得後繼節點的線程得以運行 |
CONDITION | 值爲-2,節點在等待隊列中,節點線程等待在Condition上,當其餘線程對Condition調用了signal()方法後,該節點將會從等待隊列中轉移到同步隊列中,加入到同步狀態的競爭中 |
PROPAGATE | 值爲-3,表示下一次共享式同步狀態獲取會無條件的傳播下去,好比若是頭節點獲取到共享式同步狀態,判斷狀態是PROPAGATE,會繼續調用doReleaseShared,使得後繼節點繼續獲取鎖 |
INITIAL | 值爲 0,表示初始狀態(這個應該是老版本中的代碼中存在,目前查看jdk1.8已經沒有顯示聲明INITIAL狀態,由於初始化時候,int變量默認就是0) |
分析同步器的屬性,咱們能夠大概畫出構造器的隊列示意圖,以下:併發
首先同步器聲明瞭頭節點和尾部節點,head節點指向一個node節點表示該節點是隊列的頭部節點,tail節點指向一個node節點表示該節點是尾部節點,同時,每一個節點都有pre和next屬性,指向node節點,而後如圖所示構建成一個FIFO雙向鏈表式隊列。下面咱們查看下同步器經常使用主要方法工具
方法名稱 | 功能 |
---|---|
compareAndSetState(int expect, int update) | CAS進行設置同步狀態 |
enq(final Node node) | 循環入等待隊列,直到入隊成功爲止 |
addWaiter(Node mode) | 以當前線程建立一個尾部節點,並加入到尾部 |
unparkSuccessor(Node node) | 喚醒節點的後繼節點 |
doReleaseShared() | 釋放共享模式下的同步狀態 |
setHeadAndPropagate(Node node, int propagate) | 設置頭節點,並繼續傳播同步許可 |
release(int arg) | 獨佔式釋放同步狀態 |
acquireShared(int arg) | 共享式釋放同步狀態 |
hasQueuedPredecessors() | 判斷是否有比當前線程等待更久的線程(用於公平鎖) |
以上是同步器主要的方法,咱們接下來會對上述部分方法進行重點分析,要了解同步器如何完成加鎖,等待獲取鎖,釋放鎖的功能,咱們先回顧上一篇文章分析ReentranLock的Lock()方法,實現源碼以下:post
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
複製代碼
咱們發現重點是acquire(1)方法,該方法是父類也就是同步器提供的,源碼以下:性能
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
複製代碼
源碼其實能夠拆分爲三部分:ui
tryAcquire(arg)
嘗試獲取鎖addWaiter(Node.EXCLUSIVE), arg)
以當前線程構建成節點添加到隊列尾部acquireQueued(final Node node, int arg)
讓節點以死循環去獲取同步狀態,獲取成功就退出循環其實解析爲三部分就很清楚這個方法的做用了,首先嚐試獲取鎖,獲取不到就把本身添加到尾部,而後在隊列中死循環去獲取鎖,最重要的部分就是
acquireQueued(final Node node, int arg)
,源碼以下:this
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);
}
}
複製代碼
//該方法主要靠前驅節點判斷當前線程是否應該被阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* 若是前任節點的狀態等於SIGNAL,
* 說明前任節點獲取到了同步狀態,當前節點應該被阻塞,返回true
*/
return true;
if (ws > 0) {
/*
* 前任節點被取消
*/
do {//循環查找取消節點的前任節點,
//直到找到不是取消狀態的節點,而後剔除是取消狀態的節點,
//關聯前任節點的下一個節點爲當前節點
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* CAS設置前任節點等待狀態爲SIGNAL,
* 設置成功表示當前節點應該被阻塞,下一次循環調用就會
* return true
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
複製代碼
//把當前線程掛起,從而阻塞住線程的調用棧,
//同時返回當前線程的中斷狀態。
//其內部則是調用LockSupport工具類的park()方法來阻塞該方法。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//阻塞線程
return Thread.interrupted();
}
複製代碼
以上就是lock.lock()的加鎖過程,咱們總結分析下:spa
//釋放鎖
public void unlock() {
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;
}
//該方法和以前分析的代碼相似,主要是設置Sate狀態
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);//設置同步狀態佔有線程爲null
}
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 取到當前節點的下一個節點,若是節點不爲空就喚醒阻塞的線程
//若是節點爲空,或者節點是取消狀態,那麼就循環從尾部節點找到
//當前節點的下一個節點喚醒
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);//喚醒阻塞的線程
}
複製代碼
以上就是對lock.unlock()分析,一樣咱們總結分析下
LockSupport.unpark(s.thread)
來喚醒線程的,LockSupport
主要依託於sun.misc.Unsafe
類來實現的,該類提供了操做系統硬件級別的方法,不在本文討論中。CountDownLatch
,Semaphore
,CyclicBarrier(依賴ReentrantLock)
等。下一篇《Java鎖之ReentrantReadWriteLock》繼續細化分析,分析讀鎖和寫鎖,分析ReentrantLock是如何實現讀鎖的重複獲取,鎖降級等功能的。