目錄
AQS(AbstractQueuedSynchronizer)
轉自:https://blog.csdn.net/u011212394/article/details/82314489
Lock
在jdk1.5以後,增加了juc併發包且提供了Lock接口用來實現鎖的功能,它除了提供了與synchroinzed關鍵字類似的同步功能,還提供了比synchronized更靈活api實現。
ReentrantLock
可重入鎖。如果當前線程t1通過調用lock方法獲取了鎖之後,再次調用lock,是不會再阻塞去獲取鎖的,直接增加重入次數就行了。與每次lock對應的是unlock,unlock會減少重入次數,重入次數減爲0纔會釋放鎖。
ReentrantReadWriteLock
- 可重入讀寫鎖。讀寫鎖維護了一個讀鎖,一個寫鎖。
- 讀鎖同一時刻允許多個讀線程訪問。
- 寫鎖同一時刻只允許一個寫線程,其他讀/寫線程都需要阻塞。
Lock和synchronized的簡單對比
AQS(AbstractQueuedSynchronizer)
Lock之所以能實現線程安全的鎖,主要的核心是AQS,AQS提供了一個FIFO隊列,可以看做是一個用來實現鎖以及其他需要同步功能的框架。AQS的使用依靠繼承來完成,子類通過繼承自AQS並實現所需的方法來管理同步狀態。例如常見的ReentrantLock,CountDownLatch等。
從使用上來說,AQS的功能可以分爲兩種:獨佔和共享。
獨佔鎖模式下,每次只能有一個線程持有鎖,ReentrantLock就是以獨佔方式實現的互斥鎖。
共享鎖模式下,允許多個線程同時獲取鎖,併發訪問共享資源,比如ReentrantReadWriteLock。
AQS的內部實現
同步器依賴內部的同步隊列(一個FIFO雙向隊列)來完成同步狀態的管理,當前線程獲取同步狀態失敗時,同步器會將當前線程以及等待狀態等信息構造成爲一個節點(Node)並將其加入同步隊列,同時會阻塞當前線程,當同步狀態釋放時,會把首節點中的線程喚醒,使其再次嘗試獲取同步狀態。
Node的主要屬性:
static final class Node {
//表示節點的狀態,包含SIGNAL、CANCELLED、CONDITION、PROPAGATE、INITIAL
volatile int waitStatus;
//前繼節點
volatile Node prev;
//後繼節點
volatile Node next;
//當前線程
volatile Thread thread;
//存儲在condition隊列中的後繼節點
Node nextWaiter;
}
waitStatus節點的幾種狀態:
CANCELLED,值爲1,由於在同步隊列中等待的線程等待超時或者被中斷,需要從同步隊列中取消等待,節點進入該狀態將不會變化。
SIGNAL,值爲-1,後繼節點的線程處於等待狀態,而當前節點的線程如果釋放了同步狀態或者被取消,將會通知後繼節點,使後繼節點得以運行。
CONDITION,值爲-2,節點在等待隊列中,節點線程等待在Condition上,當其他線程對Condition調用了signal()方法後,該節點將會從等待隊列中轉移到同步隊列中,加入到對同步狀態的獲取中。
PROPAGATE,值爲-3,表示下一次共享式同步狀態獲取將會無條件地被傳播下去。
INITAL,值爲0,初始狀態。
設置尾節點:
當一個線程成功地獲取了同步狀態(或者鎖),其他線程將無法獲取,轉而被構造成爲節點並加入同步隊列,而這個過程必須保證線程安全,因此同步器提供了一個基於CAS的設置尾節點的方法:compareAndSetTail(Node expect,Nodeupdate),它需要傳遞當前線程“認爲”的尾節點和當前節點,只有設置成功後,當前節點才正式與之前的尾節點建立關聯。
/**
* CAS tail field. Used only by enq.
*/
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
設置首節點:
同步隊列遵循FIFO,首節點是獲取同步狀態成功的節點,首節點的線程在釋放同步狀態時,將會喚醒後繼節點,而後繼節點將會在獲取同步狀態成功時將自己設置爲首節點。
設置首節點是通過獲取同步狀態成功的線程來完成的,由於只有一個線程能夠成功獲取到同步狀態,因此設置頭節點的方法並不需要使用CAS來保證,它只需要將首節點設置成爲原首節點的後繼節點並斷開原首節點的next引用即可。
/**
* CAS head field. Used only by enq.
*/
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
CAS
在AQS中,除了本身的鏈表結構以外,還有一個很關鍵的功能,就是CAS。這個功能可以保證線程在高併發的情況下能安全的加入到AQS隊列中。
在AQS設置首節點和尾節點的方法中,都用到了unsafe.compareAndSwapObject方法。Unsafe類是在sun.misc包下,不屬於Java標準,但是很多Java的基礎類庫,包括一些被廣泛使用的高性能開發庫都是基於Unsafe類開發的,比如Netty、Hadoop、Kafka等,Unsafe類可認爲是Java中留下的後門,提供了一些底層操作,如直接內存訪問、線程調度等。
compareAndSwapObject是一個native方法
/**
* var1:需要改變的對象
* var2:偏移量
* var4:期待值
* var5:更新後的值
*/
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
它的作用是,若當前時刻的值var1等於預期值var4,則更新爲期望值 var5,如果更新成功,則返回true,否則返回false。
ReentrantLock的實現原理
ReentrantLock - > lock()
public void lock() {
sync.lock();
}
這個是獲取鎖的入口。sync是一個實現了AQS的抽象類,這個類的主要作用是用來實現同步控制的,sync有兩個實現,一個是NonfairSync(非公平鎖)、另一個是FailSync(公平鎖)。
ReentrantLock - > NonfairSync.lock
final void lock() {
/*
通過cas算法去改變state的值
state=0表示無鎖狀態,state>0表示有鎖狀態
這是跟公平鎖的主要區別,一上來就試探鎖是否空閒,如果可以插隊,則設置獲得鎖的線程爲當前線程
*/
if (compareAndSetState(0, 1))
//exclusiveOwnerThread屬性是AQS從父類AbstractOwnableSynchronizer中繼承的屬性,用來保存當前佔用鎖的線程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); //嘗試去獲取鎖
}
當鎖被佔用時,執行acquire(1),以下都是對這個方法的詳細分析
/**
* 嘗試獲取獨佔鎖,獲取成功則返回
* 獲取失敗則繼續自旋獲取鎖,並且判斷中斷標識,如果中斷標識爲true,則設置線程中斷
* addWaiter方法把當前線程封裝成Node,並添加到隊列的尾部
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
AQS - > tryAcquire
該方法嘗試獲取鎖,如果成功就返回,如果不成功,則把當前線程和等待狀態信息構造成一個Node節點,並將節點放入同步隊列的尾部。然後爲同步隊列中的當前節點循環等待獲取鎖,直到成功。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
AQS - > nofairTryAcquire
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//獲取當前的狀態,默認情況下是0表示無鎖狀態
int c = getState();
if (c == 0) {
//通過cas來改變state狀態的值,如果更新成功,表示獲取鎖成功
//這個操作外部方法lock()就做過一次,這裏再做只是爲了再嘗試一次,儘量以最簡單的方式獲取鎖。
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;
}
//如果狀態不爲0,且當前線程不是owner,則返回false
return false;
}
AQS - >addWaiter
當前線程來請求鎖的時候,如果鎖已經被其他線程鎖持有,當前線程會進入這個方法,這個方法主要是把當前線程封裝成node,添加到AQS的鏈表中。
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
//創建一個獨佔的Node節點,mode爲排他模式
Node node = new Node(Thread.currentThread(), mode);
// 嘗試快速入隊,如果失敗則降級至full enq
Node pred = tail; // tail是AQS的中表示同步隊列隊尾的屬性,剛開始爲null,所以進行enq(node)方法
if (pred != null) {
node.prev = pred;
// 防止有其他線程修改tail,使用CAS進行修改,如果失敗則降級至full enq
if (compareAndSetTail(pred, node)) {
pred.next = node; // 如果成功之後舊的tail的next指針再指向新的tail,成爲雙向鏈表
return node;
}
}
// 如果隊列爲null或者CAS設置新的tail失敗
enq(node);
return node;
}
AQS- >enq
enq就是通過自旋操作把當前節點加入到隊列中。
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
//無限循環,爲什麼採用for(;;),是因爲它執行的指令少,不佔用寄存器
for (;;) {
Node t = tail; //第一次循環時,head, tail都爲null
//如果tail爲null則說明隊列首次使用,需要進行初始化
if (t == null) { // Must initialize
//設置頭節點,如果失敗則存在競爭,留至下一輪循環
if (compareAndSetHead(new Node()))
//用CAS的方式創建一個空的Node作爲頭結點,因爲此時隊列中只一個頭結點,所以tail也指向head
tail = head;
//第一次循環執行結束
} else {
//進行第二次循環時,tail不爲null,進入else區域。
//將當前線程的Node結點的prev指向tail,然後使用CAS將tail指向Node
//這部分代碼和addWaiter代碼一樣,將當前節點添加到隊列
node.prev = t;
if (compareAndSetTail(t, node)) {
//t此時指向tail,所以可以CAS成功,將tail重新指向Node。
//此時t爲更新前的tail的值,即指向空的頭結點,t.next=node,就將頭結點的後續結點指向Node,返回頭結點
t.next = node;
return t;
}
}
}
}
AQS- >acquireQueued
前面addWaiter返回了插入的節點,作爲acquireQueued方法的入參,這個方法主要用於爭搶鎖。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//獲取prev節點,若爲null即刻拋出NullPointException
final Node p = node.predecessor();
//如果前驅爲head纔有資格進行鎖的搶奪
if (p == head && tryAcquire(arg)) {
//獲取鎖成功後就不需要再進行同步操作了,獲取鎖成功的線程作爲新的head節點
setHead(node);
//凡是head節點,head.thread與head.prev永遠爲null, 但是head.next不爲null
p.next = null; // help GC
failed = false; //獲取鎖成功
return interrupted;
}
//如果獲取鎖失敗,則根據節點的waitStatus決定是否需要掛起線程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//若前面爲true,則執行掛起,待下次喚醒的時候檢測中斷的標誌
interrupted = true;
}
} finally {
if (failed)
//如果拋出異常則取消鎖的獲取,進行出隊(sync queue)操作
cancelAcquire(node);
}
}
原來的head節點釋放鎖以後,會從隊列中移除,原來head節點的next節點會成爲head節點。
AQS- >shouldParkAfterFailedAcquire
從上面的分析可以看出,只有隊列的第二個節點可以有機會爭用鎖,如果成功獲取鎖,則此節點晉升爲頭節點。對於第三個及以後的節點,if (p == head)條件不成立,首先進行shouldParkAfterFailedAcquire(p, node)操作。
shouldParkAfterFailedAcquire方法是判斷一個爭用鎖的線程是否應該被阻塞。它首先判斷一個節點的前置節點的狀態是否爲Node.SIGNAL,如果是,是說明此節點已經將狀態設置-如果鎖釋放,則應當通知它,所以它可以安全的阻塞了,返回true。
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; //前繼節點的狀態
if (ws == Node.SIGNAL) //如果是SIGNAL狀態,意味着當前線程需要被unpark喚醒
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
/*
* 如果前節點的狀態大於0,即爲CANCELLED狀態時,則會從前節點開始逐步循環找到一個沒有
* 被CANCELLED節點設置爲當前節點的前節點,返回false。在下次循環執行
* shouldParkAfterFailedAcquire時,返回true。
* 這個操作實際是把隊列中CANCELLED節點剔除掉。
*/
if (ws > 0) {
/*
* 如果前繼節點是“取消”狀態,則設置 “當前節點”的 “當前前繼節點” 爲 “‘原前繼節
* 點'的前繼節點”。
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { //如果前繼節點爲“0”或者“共享鎖”狀態,則設置前繼節點爲SIGNAL狀態。
/*
* 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;
}
AQS- >parkAndCheckInterrupt
如果shouldParkAfterFailedAcquire返回了true,acquireQueued會繼續執行parkAndCheckInterrupt()方法,它是通過LockSupport.park(this)將當前線程掛起到WATING狀態,它需要等待一箇中斷、unpark方法來喚醒它,通過這樣一種FIFO的機制的等待,來實現了Lock的操作。
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
//LockSupport提供park()和unpark()方法實現阻塞線程和解除線程阻塞
LockSupport.park(this);
return Thread.interrupted();
}
ReentrantLock - > unlock()
下面是釋放鎖的過程。先調用AQS - > release方法,這個方法裏面做兩件事,1.釋放鎖 ;2.喚醒park的線程。
/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
public void unlock() {
sync.release(1);
}
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
ReentrantLock - > Sync.tryRelease
這個動作可以認爲就是一個設置鎖狀態的操作,而且是將狀態減掉傳入的參數值(參數是1),如果結果狀態爲0,就將排它鎖的Owner設置爲null,以使得其它的線程有機會進行執行。 在排它鎖中,加鎖的時候狀態會增加1(當然可以自己修改這個值),在解鎖的時候減掉1,同一個鎖,在可以重入後,可能會被疊加爲2、3、4這些值,只有unlock()的次數與lock()的次數對應纔會將Owner線程設置爲空,而且也只有這種情況下才會返回true。
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 這裏是將鎖的數量減1
if (Thread.currentThread() != getExclusiveOwnerThread())
// 如果釋放的線程和獲取鎖的線程不是同一個,拋出非法監視器狀態異常
throw new IllegalMonitorStateException();
boolean free = false;
// 由於重入的關係,不是每次釋放鎖c都等於0,
// 直到最後一次釋放鎖時,纔會把當前線程釋放
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
lock與unlock過程總結
在獲取同步狀態時,同步器維護一個同步隊列,獲取狀態失敗的線程都會被加入到隊列中並在隊列中進行自旋;移出隊列(或停止自旋)的條件是前驅節點爲頭節點且成功獲取了同步狀態。在釋放同步狀態時,同步器調用tryRelease(int arg)方法釋放同步狀態,然後喚醒頭節點的後繼節點。
公平鎖和非公平鎖的區別
鎖的公平性是相對於獲取鎖的順序而言的,如果是一個公平鎖,那麼鎖的獲取順序就應該符合請求的絕對時間順序,也就是FIFO。 在上面分析的例子來說,只要CAS設置同步狀態成功,則表示當前線程獲取了鎖,而公平鎖則不一樣,差異點有兩個
final void lock() {
acquire(1);
}
非公平鎖在獲取鎖的時候,會先通過CAS進行搶佔,而公平鎖則不會。
ReentrantLock - > FairSync.tryAcquire
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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,則表示有線程比當前線程更早地請求獲取鎖,因此需要等待前驅線程獲取並釋放鎖之後才能繼續獲取鎖。
Condition
我們知道任意一個Java對象,都擁有一組監視器方法(定義在java.lang.Object上),主要包括wait()、notify()以及notifyAll()方法,這些方法與synchronized同步關鍵字配合,可以實現等待/通知模式。
JUC包提供了Condition來對鎖進行精準控制,Condition是一個多線程協調通信的工具類,可以讓某些線程一起等待某個條件(condition),只有滿足條件時,線程纔會被喚醒。
condition中兩個最重要的方法:
- await,把當前線程阻塞掛起;
- signal,喚醒阻塞的線程。
AQS - > ConditionObject.await()
/**
* Implements interruptible condition wait.
* <ol>
* <li> If current thread is interrupted, throw InterruptedException.
* <li> Save lock state returned by {@link #getState}.
* <li> Invoke {@link #release} with saved state as argument,
* throwing IllegalMonitorStateException if it fails.
* <li> Block until signalled or interrupted.
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* <li> If interrupted while blocked in step 4, throw InterruptedException.
* </ol>
*/
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//創建一個新的節點,節點狀態爲condition,採用的數據結構仍然是鏈表
Node node = addConditionWaiter();
//釋放當前的鎖,得到鎖的狀態,並喚醒AQS隊列中的一個線程
int savedState = fullyRelease(node);
int interruptMode = 0;
//如果當前節點沒有在同步隊列上,即還沒有被signal,則將當前線程阻塞
//isOnSyncQueue 判斷當前 node 狀態,如果是 CONDITION 狀態,或者不在隊列上了,就繼續阻塞。
//還在隊列上且不是 CONDITION 狀態了,就結束循環和阻塞
while (!isOnSyncQueue(node)) { //第一次判斷的是false,因爲前面已經釋放鎖了
LockSupport.park(this); //第一次總是 park 自己,開始阻塞等待
//線程判斷自己在等待過程中是否被中斷了
//如果沒有中斷,則再次循環,會在 isOnSyncQueue 中判斷自己是否在隊列上
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//當這個線程醒來,會嘗試拿鎖, 當 acquireQueued 返回 false 就是拿到鎖了
// interruptMode != THROW_IE -> 表示這個線程沒有成功將 node 入隊,但 signal 執行了 enq 方法讓其入隊了
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
// 將這個變量設置成 REINTERRUPT
interruptMode = REINTERRUPT;
//如果 node 的下一個等待者不是null, 則進行清理,清理 Condition 隊列上的節點
//如果是 null ,就沒有什麼好清理的了
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//如果線程被中斷了,需要拋出異常.或者什麼都不做
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
AQS - > ConditionObject.signal()
調用Condition的signal()方法,將會喚醒在等待隊列中等待時間最長的節點(首節點),在喚醒節點之前,會將節點移到同步隊列中。
/**
* Moves the longest-waiting thread, if one exists, from the
* wait queue for this condition to the wait queue for the
* owning lock.
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signal() {
//先判斷當前線程是否獲得了鎖
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter; //拿到 Condition 隊列上第一個節點
if (first != null)
doSignal(first);
}
/**
* Removes and transfers nodes until hit non-cancelled one or
* null. Split out from signal in part to encourage compilers
* to inline the case of no waiters.
* @param first (non-null) the first node on condition queue
*/
private void doSignal(Node first) {
do {
//如果第一個節點的下一個節點是 null,那麼最後一個節點也是 null
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null; //將 next 節點設置成 null
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
AQS - > transferForSignal
該方法先是 CAS 修改了節點狀態,如果成功,就將這個節點放到 AQS 隊列中,然後喚醒這個節點上的線程。此時,那個節點就會在 await 方法中甦醒。
/**
* Transfers a node from a condition queue onto sync queue.
* Returns true if successful.
* @param node the node
* @return true if successfully transferred (else the node was
* cancelled before signal)
*/
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
// 如果上一個節點的狀態被取消了, 或者嘗試設置上一個節點的狀態爲 SIGNAL 失敗了
// SIGNAL 表示: 他的next 節點需要停止阻塞
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); // 喚醒輸入節點上的線程
return true;
}
LockSupport
LockSupport類是Java6引入的一個類,提供了基本的線程同步原語。LockSupport實際上是調用了Unsafe類裏的函數,歸結到Unsafe裏,只有兩個函數:
public native void unpark(Thread jthread);
public native void park(boolean isAbsolute, long time);
unpark函數爲線程提供“許可(permit)”,線程調用park函數則等待“許可”。這個有點像信號量,但是這個“許可”是不能疊加的,“許可”是一次性的。
permit相當於0/1的開關,默認是0,調用一次unpark就加1變成了1.調用一次park會消費permit,又會變成0。 如果再調用一次park會阻塞,因爲permit已經是0了。直到permit變成1.這時調用unpark會把permit設置爲1。每個線程都有一個相關的permit,permit最多隻有一個,重複調用unpark不會累積。
在使用LockSupport之前,我們對線程做同步,只能使用wait和notify,但是wait和notify其實不是很靈活,並且耦合性很高,調用notify必須要確保某個線程處於wait狀態,而park/unpark模型真正解耦了線程之間的同步,先後順序沒有沒有直接關聯,同時線程之間不再需要一個Object或者其它變量來存儲狀態,不再需要關心對方的狀態。
總結
1. 公平鎖
- 線程1進來時會調用acquire方式嘗試獲取鎖,此時會獲取成功,把線程設置爲當前前程,並更改計數
- 線程2進來會判斷嘗試獲取鎖,如果state=0&&hasQueuedPredecessors 沒有等待隊列,則當前線程可以嘗試CAS,如果state=0但是有等待隊列,則認爲輪不到t2執行,調用addWaiter追加到隊列(這裏區分是首次追加和非首次追加,首次追加需要構造出一個虛擬節點),構造完成後return,然後調用acquireQueued進行循環,順便再判斷剛添加進來這個節點是否可以被執行,如果可以,將t2出隊,並移動head指針,如果不可以被執行,則設置阻塞狀態。
- 線程3進來,同線程2,只不過這裏會調用addWaiter直接追加到隊列尾部(因爲已經有線程2在排隊,輪不到線程3執行了)。
- 線程1釋放鎖時,會給state進行減1操作,判斷head指針即判斷隊列中是否有排隊的線程,有則喚醒。
2.非公平鎖
- 非公平鎖在調用 lock 後,首先就會調用 CAS 進行一次搶鎖,如果這個時候恰巧鎖沒有被佔用,那麼直接就獲取到鎖返回了。
- 非公平鎖在 CAS 失敗後,和公平鎖一樣都會進入到 tryAcquire 方法,在 tryAcquire 方法中,如果發現鎖這個時候被釋放了(state == 0),非公平鎖會直接 CAS 搶鎖,但是公平鎖會判斷等待隊列是否有線程處於等待狀態,如果有則不去搶鎖,乖乖排到後面。
公平鎖和非公平鎖就這兩點區別,如果這兩次 CAS 都不成功,那麼後面非公平鎖和公平鎖是一樣的,都要進入到阻塞隊列等待喚醒。
相對來說,非公平鎖會有更好的性能,因爲它的吞吐量比較大。當然,非公平鎖讓獲取鎖的時間變得更加不確定,可能會導致在阻塞隊列中的線程長期處於飢餓狀態。