LockSupport簡介
LockSupprot 用來阻塞和喚醒線程,底層實現依賴於 Unsafe 類。
LockSupport用來創建鎖和其他同步類的基本線程阻塞原語。簡而言之,當調用LockSupport.park時,表示當前線程將會等待,直至獲得許可,當調用LockSupport.unpark時,必須把等待獲得許可的線程作為參數進行傳遞,好讓此線程繼續運行。在AQS中大量使用,AQS最終都是使用LockSupport來阻塞線程的。
該類包含一組用於阻塞和喚醒線程的靜態方法,這些方法主要是圍繞 park 和 unpark 展開,話不多説,直接來看一個簡單的例子吧。
public class LockSupportDemo1 {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
// 創建一個線程從1數到1000
Thread counterThread = new Thread(() -> {
for (int i = 1; i <= 1000; i++) {
System.out.println(i);
if (i == 500) {
// 當數到500時,喚醒主線程
LockSupport.unpark(mainThread);
}
}
});
counterThread.start();
// 主線程調用park
LockSupport.park();
System.out.println("Main thread was unparked.");
}
}
上面的代碼中,當 counterThread 數到 500 時,它會喚醒 mainThread。而 mainThread 在調用 park 方法時會被阻塞,直到被 unpark。
LockSupport 中的方法不多,這裏將這些方法做一個總結:
阻塞線程
void park():阻塞當前線程,如果調用 unpark 方法或線程被中斷,則該線程將變得可運行。請注意,park 不會拋出 InterruptedException,因此線程必須單獨檢查其中斷狀態。void park(Object blocker):功能同方法 1,入參增加一個 Object 對象,用來記錄導致線程阻塞的對象,方便問題排查。void parkNanos(long nanos):阻塞當前線程一定的納秒時間,或直到被 unpark 調用,或線程被中斷。void parkNanos(Object blocker, long nanos):功能同方法 3,入參增加一個 Object 對象,用來記錄導致線程阻塞的對象,方便問題排查。void parkUntil(long deadline):阻塞當前線程直到某個指定的截止時間(以毫秒為單位),或直到被 unpark 調用,或線程被中斷。void parkUntil(Object blocker, long deadline):功能同方法 5,入參增加一個 Object 對象,用來記錄導致線程阻塞的對象,方便問題排查。
喚醒線程
void unpark(Thread thread):喚醒一個由 park 方法阻塞的線程。如果該線程未被阻塞,那麼下一次調用 park 時將立即返回。這允許“先發制人”式的喚醒機制。
實際上,LockSupport 阻塞和喚醒線程的功能依賴於 sun.misc.Unsafe,這是一個很底層的類,比如 LockSupport 的 park 方法是通過 unsafe.park() 方法實現的。
LockSupport源碼分析
類的屬性
public class LockSupport {
// Hotspot implementation via intrinsics API
private static final sun.misc.Unsafe UNSAFE;
// 表示內存偏移地址
private static final long parkBlockerOffset;
// 表示內存偏移地址
private static final long SEED;
// 表示內存偏移地址
private static final long PROBE;
// 表示內存偏移地址
private static final long SECONDARY;
static {
try {
// 獲取Unsafe實例
UNSAFE = sun.misc.Unsafe.getUnsafe();
// 線程類類型
Class<?> tk = Thread.class;
// 獲取Thread的parkBlocker字段的內存偏移地址
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
// 獲取Thread的threadLocalRandomSeed字段的內存偏移地址
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
// 獲取Thread的threadLocalRandomProbe字段的內存偏移地址
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
// 獲取Thread的threadLocalRandomSecondarySeed字段的內存偏移地址
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
}
}
説明: UNSAFE字段表示sun.misc.Unsafe類,一般程序中不允許直接調用,而long型的表示實例對象相應字段在內存中的偏移地址,可以通過該偏移地址獲取或者設置該字段的值。
類的構造函數
// 私有構造函數,無法被實例化
private LockSupport() {}
説明: LockSupport只有一個私有構造函數,無法被實例化。
核心函數分析
在分析LockSupport函數之前,先引入sun.misc.Unsafe類中的park和unpark函數,因為LockSupport的核心函數都是基於Unsafe類中定義的park和unpark函數,下面給出兩個函數的定義:
public native void park(boolean isAbsolute, long time);
public native void unpark(Thread thread);
説明: 對兩個函數的説明如下:
-
park函數,阻塞線程,並且該線程在下列情況發生之前都會被阻塞:
-
調用unpark函數,釋放該線程的許可。
-
該線程被中斷。
-
設置的時間到了。並且,當time為絕對時間時,isAbsolute為true,否則,isAbsolute為false。當time為0時,表示無限等待,直到unpark發生。
-
-
unpark函數,釋放線程的許可,即激活調用park後阻塞的線程。這個函數不是安全的,調用這個函數時要確保線程依舊存活。
park函數
park函數有兩個重載版本,方法摘要如下
public static void park();
public static void park(Object blocker);
説明: 兩個函數的區別在於park()函數沒有沒有blocker,即沒有設置線程的parkBlocker字段。park(Object)型函數如下。
public static void park(Object blocker) {
// 獲取當前線程
Thread t = Thread.currentThread();
// 設置Blocker
setBlocker(t, blocker);
// 獲取許可
UNSAFE.park(false, 0L);
// 重新可運行後再此設置Blocker
setBlocker(t, null);
}
説明: 調用park函數時,首先獲取當前線程,然後設置當前線程的parkBlocker字段,即調用setBlocker函數,之後調用Unsafe類的park函數,之後再調用setBlocker函數。那麼問題來了,為什麼要在此park函數中要調用兩次setBlocker函數呢? 原因其實很簡單,調用park函數時,當前線程首先設置好parkBlocker字段,然後再調用Unsafe的park函數,此後,當前線程就已經阻塞了,等待該線程的unpark函數被調用,所以後面的一個setBlocker函數無法運行,unpark函數被調用,該線程獲得許可後,就可以繼續運行了,也就運行第二個setBlocker,把該線程的parkBlocker字段設置為null,這樣就完成了整個park函數的邏輯。如果沒有第二個setBlocker,那麼之後沒有調用park(Object blocker),而直接調用getBlocker函數,得到的還是前一個park(Object blocker)設置的blocker,顯然是不符合邏輯的。總之,必須要保證在park(Object blocker)整個函數執行完後,該線程的parkBlocker字段又恢復為null。所以,park(Object)型函數裏必須要調用setBlocker函數兩次。setBlocker方法如下。
private static void setBlocker(Thread t, Object arg) {
// 設置線程t的parkBlocker字段的值為arg
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
説明: 此方法用於設置線程t的parkBlocker字段的值為arg。
另外一個無參重載版本,park()函數如下。
public static void park() {
// 獲取許可,設置時間為無限長,直到可以獲取許可
UNSAFE.park(false, 0L);
}
説明: 調用了park函數後,會禁用當前線程,除非許可可用。在以下三種情況之一發生之前,當前線程都將處於休眠狀態,即下列情況發生時,當前線程會獲取許可,可以繼續運行。
-
其他某個線程將當前線程作為目標調用 unpark。
-
其他某個線程中斷當前線程。
-
該調用不合邏輯地(即毫無理由地)返回。
parkNanos函數
此函數表示在許可可用前禁用當前線程,並最多等待指定的等待時間。具體函數如下。
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) { // 時間大於0
// 獲取當前線程
Thread t = Thread.currentThread();
// 設置Blocker
setBlocker(t, blocker);
// 獲取許可,並設置了時間
UNSAFE.park(false, nanos);
// 設置許可
setBlocker(t, null);
}
}
説明: 該函數也是調用了兩次setBlocker函數,nanos參數表示相對時間,表示等待多長時間。
parkUntil函數
此函數表示在指定的時限前禁用當前線程,除非許可可用, 具體函數如下:
public static void parkUntil(Object blocker, long deadline) {
// 獲取當前線程
Thread t = Thread.currentThread();
// 設置Blocker
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
// 設置Blocker為null
setBlocker(t, null);
}
説明: 該函數也調用了兩次setBlocker函數,deadline參數表示絕對時間,表示指定的時間。
unpark函數
此函數表示如果給定線程的許可尚不可用,則使其可用。如果線程在 park 上受阻塞,則它將解除其阻塞狀態。否則,保證下一次調用 park 不會受阻塞。如果給定線程尚未啓動,則無法保證此操作有任何效果。具體函數如下:
public static void unpark(Thread thread) {
if (thread != null) // 線程為不空
UNSAFE.unpark(thread); // 釋放該線程許可
}
説明: 釋放許可,指定線程可以繼續運行。
更深入的理解
與 synchronzed 的區別
synchronzed 會使線程阻塞,線程會進入 BLOCKED 狀態,而調用 LockSupprt 方法阻塞線程會使線程進入到 WAITING 狀態。
Thread.sleep()和Object.wait()的區別
首先,我們先來看看Thread.sleep()和Object.wait()的區別,這是一個爛大街的題目了,大家應該都能説上來兩點。
-
Thread.sleep()不會釋放佔有的鎖,Object.wait()會釋放佔有的鎖;
-
Thread.sleep()必須傳入時間,Object.wait()可傳可不傳,不傳表示一直阻塞下去;
-
Thread.sleep()到時間了會自動喚醒,然後繼續執行;
-
Object.wait()不帶時間的,需要另一個線程使用Object.notify()喚醒;
-
Object.wait()帶時間的,假如沒有被notify,到時間了會自動喚醒,這時又分好兩種情況,一是立即獲取到了鎖,線程自然會繼續執行;二是沒有立即獲取鎖,線程進入同步隊列等待獲取鎖;
其實,他們倆最大的區別就是Thread.sleep()不會釋放鎖資源,Object.wait()會釋放鎖資源。
Object.wait()和Condition.await()的區別
Object.wait()和Condition.await()的原理是基本一致的,不同的是Condition.await()底層是調用LockSupport.park()來實現阻塞當前線程的。
實際上,它在阻塞當前線程之前還幹了兩件事,一是把當前線程添加到條件隊列中,二是“完全”釋放鎖,也就是讓state狀態變量變為0,然後才是調用LockSupport.park()阻塞當前線程。
Thread.sleep()和LockSupport.park()的區別
LockSupport.park()還有幾個兄弟方法——parkNanos()、parkUtil()等,我們這裏説的park()方法統稱這一類方法。
-
從功能上來説,Thread.sleep()和LockSupport.park()方法類似,都是阻塞當前線程的執行,且都不會釋放當前線程佔有的鎖資源;
-
Thread.sleep()沒法從外部喚醒,只能自己醒過來;
-
LockSupport.park()方法可以被另一個線程調用LockSupport.unpark()方法喚醒;
-
Thread.sleep()方法聲明上拋出了InterruptedException中斷異常,所以調用者需要捕獲這個異常或者再拋出;
-
LockSupport.park()方法不需要捕獲中斷異常;
-
Thread.sleep()本身就是一個native方法;
-
LockSupport.park()底層是調用的Unsafe的native方法;
Object.wait()和LockSupport.park()的區別
二者都會阻塞當前線程的運行,他們有什麼區別呢? 經過上面的分析相信你一定很清楚了,真的嗎? 往下看!
-
Object.wait()方法需要在synchronized塊中執行;
-
LockSupport.park()可以在任意地方執行;
-
Object.wait()方法聲明拋出了中斷異常,調用者需要捕獲或者再拋出;
-
LockSupport.park()不需要捕獲中斷異常;
-
Object.wait()不帶超時的,需要另一個線程執行notify()來喚醒,但不一定繼續執行後續內容;
-
LockSupport.park()不帶超時的,需要另一個線程執行unpark()來喚醒,一定會繼續執行後續內容;
park()/unpark()底層的原理是“二元信號量”,你可以把它相像成只有一個許可證的Semaphore,只不過這個信號量在重複執行unpark()的時候也不會再增加許可證,最多隻有一個許可證。
如果在wait()之前執行了notify()會怎樣?
如果當前的線程不是此對象鎖的所有者,卻調用該對象的notify()或wait()方法時拋出IllegalMonitorStateException異常;
如果當前線程是此對象鎖的所有者,wait()將一直阻塞,因為後續將沒有其它notify()喚醒它。
如果在park()之前執行了unpark()會怎樣?
線程不會被阻塞,直接跳過park(),繼續執行後續內容
LockSupport.park()會釋放鎖資源嗎?
不會,它只負責阻塞當前線程,釋放鎖資源實際上是在Condition的await()方法中實現的。