本文主要爲《Java併發編程的藝術》第三章的讀書記錄筆記java
Java SE 5以後,併發包中新增了Lock接口(以及相關實現類)用來實現鎖功能,它提供了與synchronized關鍵字相似的同步功能。可是,它們卻有如下不一樣:node
synchronized:使用synchronized的關鍵字將會隱式地獲取和釋放鎖。同時,使用什麼類型的鎖(偏向,輕量級鎖,重量級鎖)以及鎖的具體實現都是由JVM底層實現。編程
Lock接口:Lock接口相關實現類對於鎖的獲取和釋放須要顯示進行。同時,怎樣獲取和釋放是由開發者(包括JDK源碼)自行決定的。這樣的話,它就擁有了鎖獲取與釋放的可操做性、非阻塞地獲取鎖、可中斷的獲取鎖以及超時獲取鎖等多種synchronized關鍵字所不具有的同步特性。安全
Lock是一個接口,它定義了鎖獲取和釋放的基本操做,Lock的API以下:bash
Lock接口的實現如ReentrantLock基本都是經過聚合了一個 隊列同步器(AQS)的子類來完成線程訪問控制的。數據結構
抽象類AbstractQueuedSynchronizer提供了一個基礎框架,它能夠用來實現阻塞鎖和其餘相關的依賴於FIFO等待隊列同步組件(好比Semaphore)。它的實現主要依賴於一個單一的原子int變量值state來表示同步狀態。繼承的子類必須重寫AQS的幾個protected修飾的用來改變同步狀態的方法以及基於這些狀態進行鎖獲取和釋放的方法。AQS類裏面的其餘方法主要實現了排隊和阻塞的機制。繼承AQS的子類可能會維護一些其餘的狀態值,可是隻能經過getState,setState以compareAndSetState這三個方法來原子地更新管理同步狀態的state值。多線程
繼承AQS的子類被推薦定義爲阻塞鎖或者同步組件實現類的靜態內部類。AQS本身自己沒有實現任何同步接口。相反地,它僅僅是定義了若干同步狀態的獲取和釋放方法來供阻塞鎖或者同步組件的使用,來實現子類的公共方法。併發
AQS既支持獨佔式獲取同步狀態,又支持共享式獲取同步狀態,也支持二者模式具有。默認狀況下是獨佔式獲取同步狀態。框架
AQS並不關心這些不一樣模式之間的差別。不一樣模式下的等待線程共享同一個FIFO隊列。一般,AQS的實現子類要麼只支持獨佔模式,要麼只支持共享模式。可是,也有例外,好比ReadWriteLock兩種都支持。只支持獨佔模式或者是共享模式的子類不須要定義另外一種模式的方法。ide
AQS是實現鎖Lock接口或者是同步組件的關鍵,在鎖的實現中聚合AQS,利用AQS實現鎖的語義。能夠這樣理解兩者之間的關係:
同步器的設計是基於模板模式的,也就是說,使用者須要繼承AQS並重寫指定的方法,隨後將AQS組合在自定義同步組件的實現中,並調用AQS提供的模板方法,而這些模板方法將會調用使用者重寫的方法。
重寫AQS指定的方法時,須要使用AQS提供的以下3個原子操做方法來訪問或修改同步狀態:
繼承AQS必須重寫的方法以下所示:
繼承AQS實現自定義同步組件時,將會調用AQS提供的模板方法。這些模板方法以下所示,注意到這些方法都是final修飾,表示這些方法不能被重寫。
同步器提供的模板方法基本上分爲3類:獨佔式獲取與釋放同步狀態、共享式獲取與釋放同步狀態和查詢同步隊列中的等待線程狀況。自定義同步組件將使用同步器提供的模板方法來實現本身的同步語義。
獨佔鎖就是在同一時刻只能有一個線程獲取到鎖,而其餘獲取鎖的線程只能處於同步隊列中等待,只有獲取鎖的線程釋放了鎖,後繼的線程纔可以獲取鎖,以下所示:
class Mutex implements Lock {
// 僅須要將操做代理到Sync上便可
private final Sync sync = new Sync();
public void lock() {
sync.acquire(1);
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
public boolean isLocked() {
return sync.isHeldExclusively();
}
public boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
// 靜態內部類,自定義同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 是否處於佔用狀態
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 當狀態爲0的時候獲取鎖
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 釋放鎖,將狀態設置爲0
protected boolean tryRelease(int releases) {
if (getState() == 0) throw new
IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 返回一個Condition,每一個condition都包含了一個condition隊列
Condition newCondition() {
return new ConditionObject();
}
}
}
複製代碼
能夠發現:
下面在給出這個獨佔鎖的使用Demo,能夠發現它的實現沒問題,確實完成了和synchronized關鍵字同樣的同步功能。
/**
* 輸出:
* Running Thread-1
* Exit Thread-1
* Running Thread-2
* Exit Thread-2
*/
@Test
public void testCase01() throws InterruptedException {
Mutex mutex = new Mutex();
Runnable r = () -> {
mutex.lock();
System.out.println("Running " + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
e.printStackTrace();
}
mutex.unlock();
System.out.println("Exit " + Thread.currentThread().getName());
};
Thread thread1 = new Thread(r, "Thread-1");
Thread thread2 = new Thread(r, "Thread-2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
複製代碼
我在寫這個例子的時候,本身有個疑問。就是,Thread2在阻塞獲取鎖又獲取不到的時候,它的線程狀態的什麼?此時,MainThread的狀態是Waiting(在join的邏輯裏面),Thread1的線程狀態的是Timed_Waiting(正在sleep)。可是,Thread2的線程狀態呢?線程的狀態參考深刻理解Java併發編程之線程Thread。
若是,這裏不是用的本身實現的獨佔鎖Mutex,而是用的synchronized,這個時候Thread2的狀態明顯是blocked。
爲了回答這個問題,我把sleep的時間設置爲666666,同時經過jstack dump出線程狀態,很清楚的發現Thread2的狀態爲waiting,結果以下。
"Thread-2" #15 prio=5 os_prio=0 tid=0x00000000299cd000 nid=0x9498 waiting on condition [0x000000002b81e000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007177a70f0> (a concurrent.aqs.Mutex$Sync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at concurrent.aqs.Mutex.lock(Mutex.java:17)
at concurrent.aqs.MutexTest.lambda$testCase01$0(MutexTest.java:17)
at concurrent.aqs.MutexTest$$Lambda$1/385337537.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
複製代碼
接下來將從實現角度分析AQS是如何完成線程同步的,主要包括:同步隊列、獨佔式同步狀態獲取與釋放、共享式同步狀態獲取與釋放以及超時獲取同步狀態等AQS的核心數據結構與模板方法。
AQS依賴內部的同步隊列(一個FIFO雙向隊列)來完成同步狀態的管理:
同步隊列是由一個Node節點的雙向鏈表實現的,Node節點數據接口以下:
volatile int waitStatus Node節點等待狀態,包含以下狀態:
這些數值的設置只是爲了簡化使用。非負的數值節點表示該節點不用被signal。所以,大多數狀況下,不會關心這些具體數值,只會關心數值的符號。
volatile Node prev
前驅節點,當節點從隊列尾部加入同步隊列時被設置。
volatile Node next 後繼節點
Node nextWaiter 等待隊列(等待在ConditionObject上的線程節點隊列)中的後繼節點。
若是當前節點是獨佔的,這個值指向等待隊列中的下一個節點。
若是當前節點是共享的,那麼這個字段將是一個SHARED常量。
volatile Thread thread Node節點鎖包裹的線程。
Node節點是構成AQS同步隊列以及等待隊列的基礎,其中同步隊列的基本結構以下圖所示:
AQS擁有同步同步隊列的頭節點(head)和尾結點(tail)的引用(head和tail爲AQS的成員變量)
當一個線程成功獲取了同步狀態(或者鎖),其餘線程將沒法得到從而被構形成爲Node節點加入同步隊列尾部。
加入隊列的過程必需要保證線程安全(可能有多個線程都沒獲取到同步狀態須要加入同步隊列),所以AQS提供了一個基於CAS的設置尾節點的方法:compareAndSetTail(Node expect,Node update),它須要傳遞當前線程「認爲」的尾節點和當前節點,只有設置成功後,當前節點才正式與以前的尾節點創建關聯。這個過程以下圖所示。
#####獨佔式同步狀態獲取 經過調用AQS的acquire(int arg)方法能夠獲取同步狀態,該方法對中斷不敏感,也就是因爲線程獲取同步狀態失敗後進入同步隊列中,後續對線程進行中斷操做時,線程不會從同步隊列中移出,該方法代碼以下所示:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
複製代碼
上述代碼主要完成了同步狀態獲取、節點構造、加入同步隊列以及在同步隊列中自旋等待的相關工做,其主要邏輯以下:
調用自定義同步器實現的tryAcquire(int arg)方法,該方法保證線程安全的獲取同步狀態。
若是同步狀態獲取失敗,則構造同步節點(獨佔式Node.EXCLUSIVE,同一時刻只能有一個線程成功獲取同步狀態)並經過addWaiter(Node node)方法將該節點加入到同步隊列的尾部。
最後調用acquireQueued(Node node,int arg)方法,使得該節點以「死循環」的方式獲取同步狀態。若是獲取不到則阻塞節點中的線程,而被阻塞線程的喚醒主要依靠前驅節點的出隊並喚醒當前節點或阻塞線程被中斷來實現。
下面逐個分析相關函數。
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;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
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);
}
}
複製代碼
在acquireQueued(final Node node,int arg)方法中,當前線程在「死循環」中嘗試獲取同步狀態,而只有前驅節點是頭節點纔可以嘗試獲取同步狀態,這是爲何?
在上圖中,因爲非首節點線程前驅節點出隊或者被中斷而從等待狀態返回,隨後檢查本身的前驅是不是頭節點,若是是則嘗試獲取同步狀態。能夠看到節點和節點之間在循環檢查的過程當中基本不相互通訊,而是簡單地判斷本身的前驅是否爲頭節點,這樣就使得節點的釋放規則符合FIFO,而且也便於對過早通知的處理(過早通知是指前驅節點不是頭節點的線程因爲中斷而被喚醒即僞喚醒)。
獨佔式同步狀態獲取流程,也就是acquire(int arg)方法調用流程以下圖所示:
在上圖中,前驅節點爲頭節點且可以獲取同步狀態的判斷條件和線程進入等待狀態是獲取同步狀態的自旋過程。當同步狀態獲取成功以後,當前線程從acquire(int arg)方法返回,若是對於鎖這種併發組件而言,表明着當前線程獲取了鎖。
當前線程獲取同步狀態並執行了相應邏輯以後,就須要釋放同步狀態,使得後續節點可以繼續獲取同步狀態。經過調用同步器的release(int arg)方法能夠釋放同步狀態,該方法在釋放了同步狀態以後,會喚醒其後繼節點(進而使後繼節點從新嘗試獲取同步狀態)。釋放代碼以下所示:
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(Node node)方法使用LockSupport來喚醒處於等待狀態的線程。
#####共享式同步狀態獲取 共享式獲取與獨佔式獲取最主要的區別在於同一時刻可否有多個線程同時獲取到同步狀態。以文件的讀寫爲例
上圖中:
左半部分,共享式訪問資源時,其餘共享式的訪問均被容許,而獨佔式訪問被阻塞。
右半部分是獨佔式訪問資源時,同一時刻其餘訪問均被阻塞。
經過調用同步器的acquireShared(int arg)方法能夠共享式地獲取同步狀態,該方法代碼以下所示:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
複製代碼
#####共享式同步狀態釋放 與獨佔式同樣,共享式獲取也須要釋放同步狀態,經過調用releaseShared(int arg)方法能夠釋放同步狀態,該方法代碼以下所示:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
複製代碼
該方法在釋放同步狀態以後,將會喚醒後續處於等待狀態的節點。對於可以支持多個線程同時訪問的併發組件(好比Semaphore),它和獨佔式主要區別在於tryReleaseShared(int arg)方法必須確保同步狀態(或者資源數)線程安全釋放,通常是經過循環和CAS來保證的,由於釋放同步狀態的操做會同時來自多個線程。
經過調用同步器的doAcquireNanos(int arg,long nanosTimeout)方法能夠超時獲取同步狀態,即在指定的時間段內獲取同步狀態,若是獲取到同步狀態則返回true,不然,返回false。該方法提供了傳統Java同步操做(好比synchronized關鍵字)所不具有的特性。
響應中斷的獲取同步狀態:
在Java 5以前,當一個線程獲取不到鎖而被阻塞在synchronized以外時,對該線程進行中斷操做,此時該線程的中斷標誌位會被修改,但線程依舊會阻塞在synchronized上,等待着獲取鎖。在Java 5中,同步器提供了acquireInterruptibly(int arg)方法,這個方法在等待獲取同步狀態時,若是當前線程被中斷,會馬上返回,並拋出InterruptedException。
獨佔式超時獲取同步狀態過程能夠被視做響應中斷獲取同步狀態過程的「加強版」,doAcquireNanos(int arg,long nanosTimeout)方法在支持響應中斷的基礎上,增長了超時獲取的特性。針對超時獲取,主要須要計算出須要睡眠的時間間隔nanosTimeout,爲了防止過早通知,nanosTimeout計算公式爲:nanosTimeout = deadline - System.nanoTime()
,其中deadline爲計算的最晚喚醒時間,若是nanosTimeout大於0則表示超時時間未到,須要繼續睡眠nanosTimeout納秒,反之,表示已經超時,該方法代碼以下:
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
複製代碼
該方法在自旋過程當中,當節點的前驅節點爲頭節點時嘗試獲取同步狀態,若是獲取成功則從該方法返回,這個過程和獨佔式同步獲取的過程相似。
若是當前線程獲取同步狀態失敗,則判斷是否超時(nanosTimeout小於等於0表示已經超時),若是沒有超時,從新計算超時間隔nanosTimeout,而後使當前線程等待nanosTimeout納秒(當已到設置的超時時間,該線程會從LockSupport.parkNanos(Objectblocker,long nanos)方法返回)。
若是nanosTimeout小於等於spinForTimeoutThreshold(1000納秒)時,將不會使該線程進行超時等待,而是進入快速的自旋過程。緣由在於,很是短的超時等待沒法作到十分精確,若是這時再進行超時等待,相反會讓nanosTimeout的超時從總體上表現得反而不精確。所以,在超時很是短的場景下,同步器會進入無條件的快速自旋。(這種狀況下線程的阻塞和喚醒的耗時可能大於nanosTimeout)。
獨佔式超時獲取同步態的流程以下圖所示:
這裏設計一個同步工具:該工具在同一時刻,只容許至多兩個線程同時訪問,超過兩個線程的訪問將被阻塞,咱們將這個同步工具命名爲TwinsLock。
TwinsLock可以在同一時刻支持多個線程的訪問,這顯然是共享式訪問,所以,須要使用同步器提供的acquireShared(int args)方法等和Shared相關的方法,這就要求TwinsLock必須重寫tryAcquireShared(int args)方法和tryReleaseShared(int args)方法,這樣才能保證同步器的共享式同步狀態的獲取與釋放方法得以執行。
TwinsLock在同一時刻容許至多兩個線程的同時訪問,代表同步資源數爲2,這樣能夠設置初始狀態status爲2,當一個線程進行獲取,status減1,該線程釋放,則status加1,狀態的合法範圍爲0、1和2,其中0表示當前已經有兩個線程獲取了同步資源,此時再有其餘線程對同步狀態進行獲取,該線程只能被阻塞。在同步狀態變動時,須要使用compareAndSet(int expect,int update)方法作原子性保障。
經過實現Lock接口以及組合繼承AQS的子類來實現這個自定義的同步組件。通常狀況下,繼承AQS的子類會被定義爲這個同步組件類的靜態內部類。
public class TwinsLock implements Lock {
private final Sync sync = new Sync(2);
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
if (count <= 0) {
throw new IllegalArgumentException("Count must be larger than zero!");
}
setState(count);
}
@Override
protected int tryAcquireShared(int reduceCount) {
for (;;) {
int current = getState();
int newCount = current - reduceCount;
if (newCount < 0 || compareAndSetState(current, newCount)) {
return newCount;
}
}
}
@Override
protected boolean tryReleaseShared(int returnCount) {
for (;;) {
int current = getState();
int newCount = current + returnCount;
if (compareAndSetState(current, newCount)) {
return true;
}
}
}
Condition newCondition() {
return new ConditionObject();
}
}
@Override
public void lock() {
sync.acquireShared(1);
}
@Override
public void unlock() {
sync.releaseShared(1);
}
// 其餘接口方法略
}
複製代碼
在上述實例中,
AQS做爲一個橋樑,鏈接線程訪問以及同步狀態控制等底層技術與不一樣併發組件(好比Lock、CountDownLatch等)的接口語義。
在測試用例中,定義了工做者線程Worker,該線程在執行過程當中獲取鎖,當獲取鎖以後使當前線程睡眠1秒(並不釋放鎖),隨後打印當前線程名稱,最後再次睡眠1秒並釋放鎖,測試代碼以下:
public class TwinsLockTest {
Lock lock = new TwinsLock();
@Test
public void testCase01() throws InterruptedException {
for (int i = 0; i < 10; i++) {
Worker w = new Worker();
Thread thread = new Thread(w, "Worker" + i);
thread.start();
}
// 每隔1秒換行
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println();
}
}
class Worker implements Runnable {
@Override
public void run() {
while (true) {
lock.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
break;
}
}
}
}
複製代碼
運行能夠發現輸出結果以下,同一時刻只有兩個線程可以獲取到鎖,這代表TwinsLock能夠按照預期正確工做。
Worker0
Worker1
Worker3
Worker2
Worker4
Worker5
Worker7
Worker6
Worker9
Worker8
複製代碼
可重入鎖ReentrantLock,顧名思義,就是支持重進入的鎖,它表示該鎖可以支持一個線程對 資源的重複加鎖。簡單地說,重進入是指任意線程在獲取到鎖以後可以再次獲取該鎖而不會被鎖所阻塞。
synchronized關鍵字隱式的支持重進入,好比一個synchronized修飾的遞歸方法,在方法執行時,執行線程在獲取了鎖以後仍能連續屢次地得到該鎖。而本文以前的例子Mutex卻不支持可重入性。若是某個線程獲取了鎖,會在下一次獲取鎖時出現阻塞本身的狀況。
可重入性的實現有兩個問題:
ReentrantLock是經過組合自定義繼承AQS的子類以及實現Lock接口來實現鎖的獲取與釋放,以非公平性(默認的)實現爲例,獲取同步狀態的代碼以下:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
複製代碼
該方法增長了線程再次獲取同步狀態(便可重入)的處理邏輯:經過判斷當前線程是否爲獲取鎖的線程來決定獲取操做是否成功,若是是獲取鎖的線程再次請求,則將同步狀態值進行增長並返回true,表示獲取同步狀態成功。
成功獲取鎖的線程再次獲取鎖,只是增長了同步狀態值,這也就要求ReentrantLock在釋放同步狀態時減小同步狀態值,該方法的代碼以下:
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);
}
setState(c);
return free;
}
複製代碼
公平性與否是針對獲取鎖而言的,若是一個鎖是公平的,那麼鎖的獲取順序就應該符合請求的絕對時間順序,也就是FIFO。
上面介紹的nonfairTryAcquire(int acquires)方法,對於非公平鎖,只要CAS設置同步狀態成功,則表示當前線程獲取了鎖,而公平鎖則不一樣,公平獲取鎖的代碼以下所示:
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;
}
複製代碼
該方法與nonfairTryAcquire(int acquires)比較,惟一不一樣的位置爲判斷條件多了hasQueuedPredecessors()方法,即加入了同步隊列中當前節點是否有前驅節點的判斷,若是該方法返回true,則表示有線程比當前線程更早地請求獲取鎖,所以須要等待前驅線程獲取並釋放鎖以後才能繼續獲取鎖。
公平與非公平獲取鎖的區別: