目錄
一、LockSupport
1.1、LockSupport函數列表
1.2、基本使用
先 park 再 unpark
先 unpark 再 park
1.3、特點
與 Object 的 wait & notify 相比
二、LockSupport park & unpark原理
2.1、情況一,先調用park,再調用unpark
park 操作
unpark 操作
2.2、情況二,先調用unpark,再調用park
三、LockSupport Java源碼解析
3.1 變量說明
變量是如何獲取其實例對象的?
3.2 構造方法
3.3 兩個特殊的方法
3.4 常用方法
1、unpark(Thread thread)方法
2、park(Object blocker)方法 和?park()方法
park(Object blocker)函數中要調用兩次setBlocker函數
3、parkNanos(Object blocker, long nanos)方法 和?parkNanos(long nanos)方法
4、parkUntil(Object blocker, long deadline)方法 和?parkUntil(long deadline)方法
總結:
一、LockSupport
LockSupport是JDK中比較底層的類,用來創建鎖和其他同步工具類的基本線程阻塞原語。
Java鎖和同步器框架的核心AQS:AbstractQueuedSynchronizer,就是通過調用LockSupport.park()
和LockSupport.unpark()
實現線程的阻塞和喚醒的。LockSupport很類似于二元信號量(只有1個許可證可供使用),如果這個許可還沒有被占用,當前線程獲取許可并繼續執行;如果許可已經被占用,當前線程阻塞,等待獲取許可。
LockSupport中的park()
?和?unpark()
?的作用分別是阻塞線程和解除阻塞線程,而且park()
和unpark()
不會遇到“Thread.suspend 和 Thread.resume所可能引發的死鎖”問題。因為park()
?和?unpark()
有許可的存在;調用?park()
?的線程和另一個試圖將其?unpark()
?的線程之間的競爭將保持活性。?
1.1、LockSupport函數列表
public class LockSupport {// 返回提供給最近一次尚未解除阻塞的 park 方法調用的 blocker 對象,如果該調用不受阻塞,則返回 null。static Object getBlocker(Thread t);// 為了線程調度,禁用當前線程,除非許可可用。static void park();// 為了線程調度,在許可可用之前禁用當前線程。static void park(Object blocker);// 為了線程調度禁用當前線程,最多等待指定的等待時間,除非許可可用。static void parkNanos(long nanos);// 為了線程調度,在許可可用前禁用當前線程,并最多等待指定的等待時間。static void parkNanos(Object blocker, long nanos);// 為了線程調度,在指定的時限前禁用當前線程,除非許可可用。static void parkUntil(long deadline);// 為了線程調度,在指定的時限前禁用當前線程,除非許可可用。static void parkUntil(Object blocker, long deadline);// 如果給定線程的許可尚不可用,則使其可用。static void unpark(Thread thread);
}
說明:LockSupport是通過調用Unsafe函數中的接口實現阻塞和解除阻塞的。?
1.2、基本使用
// 暫停當前線程
LockSupport.park();
// 恢復某個線程的運行
LockSupport.unpark(暫停線程對象)
先 park 再 unpark
Thread t1 = new Thread(() -> {log.debug("start...");sleep(1);log.debug("park...");LockSupport.park();log.debug("resume...");
},"t1");
t1.start();
sleep(2);
log.debug("unpark...");
LockSupport.unpark(t1);
輸出:
18:42:52.585 c.TestParkUnpark [t1] - start...
18:42:53.589 c.TestParkUnpark [t1] - park...
18:42:54.583 c.TestParkUnpark [main] - unpark...
18:42:54.583 c.TestParkUnpark [t1] - resume...
先 unpark 再 park
Thread t1 = new Thread(() -> {log.debug("start...");sleep(2);log.debug("park...");LockSupport.park();log.debug("resume...");
}, "t1");
t1.start();
sleep(1);
log.debug("unpark...");
LockSupport.unpark(t1);
輸出:
18:43:50.765 c.TestParkUnpark [t1] - start...
18:43:51.764 c.TestParkUnpark [main] - unpark...
18:43:52.769 c.TestParkUnpark [t1] - park...
18:43:52.769 c.TestParkUnpark [t1] - resume...
1.3、特點
在調用對象的Wait之前當前線程必須先獲得該對象的監視器(Synchronized),被喚醒之后需要重新獲取到監視器才能繼續執行。而LockSupport并不需要獲取對象的監視器。?
與 Object 的 wait & notify 相比
- 1、wait,notify 和 notifyAll 必須配合 Object Monitor 一起使用,而 park,unpark 不必。
- 2、park & unpark 是以線程為單位來【阻塞】和【喚醒】線程,而 notify 只能隨機喚醒一個等待線程,notifyAll是喚醒所有等待線程,但不那么【精確】。
- 3、park & unpark 可以先 unpark,而 wait & notify 不能先 notify。
因為它們本身的實現機制不一樣,所以它們之間沒有交集,也就是說LockSupport阻塞的線程,notify/notifyAll沒法喚醒。
雖然兩者用法不同,但是有一點, LockSupport 的park和Object的wait一樣也能響應中斷。
public class LockSupportTest {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {LockSupport.park();System.out.println("thread:"+Thread.currentThread().getName()+"awake");},"t1");t.start();Thread.sleep(2000);//中斷t.interrupt();}
}
二、LockSupport park & unpark原理
每個線程都會關聯一個 Parker 對象,每個 Parker 對象都各自維護了三個角色:_counter
(計數器)、?_mutex
(互斥量)、_cond
(條件變量)。?
2.1、情況一,先調用park,再調用unpark
park 操作
- 當前線程調用?
Unsafe.park()
?方法 - 檢查?
_counter
?,本情況為 0,這時,獲得?_mutex
?互斥鎖 - 線程進入?
_cond
?條件變量阻塞 -
設置?
_counter = 0
?unpark 操作
-
調用?
Unsafe.unpark(Thread_0)
?方法,設置?_counter
?為 1 - 喚醒?
_cond
?條件變量中的?Thread_0
Thread_0
?恢復運行-
設置?
_counter
?為 0?2.2、情況二,先調用unpark,再調用park
-
調用?
Unsafe.unpark(Thread_0)
?方法,設置?_counter
?為 1 - 當前線程調用?
Unsafe.park()
?方法 - 檢查?
_counter
?,本情況為 1,這時線程無需阻塞,繼續運行 -
設置?
_counter
?為 0?三、LockSupport Java源碼解析
3.1 變量說明
public class LockSupport {// Hotspot implementation via intrinsics API//unsafe常量,設置為使用Unsafe.compareAndSwapInt進行更新//UNSAFE字段表示sun.misc.Unsafe類,一般程序中不允許直接調用private static final sun.misc.Unsafe UNSAFE;//表示parkBlocker在內存地址的偏移量private static final long parkBlockerOffset;//表示threadLocalRandomSeed在內存地址的偏移量,此變量的作用暫時還不了解private static final long SEED;//表示threadLocalRandomProbe在內存地址的偏移量,此變量的作用暫時還不了解private static final long PROBE;//表示threadLocalRandomSecondarySeed在內存地址的偏移量// 作用是 可以通過nextSecondarySeed()方法來獲取隨機數private static final long SECONDARY; }
變量是如何獲取其實例對象的?
public class LockSupport {static {try {//實例化unsafe對象UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> tk = Thread.class;//利用unsafe對象來獲取parkBlocker在內存地址的偏移量parkBlockerOffset = UNSAFE.objectFieldOffset(tk.getDeclaredField("parkBlocker"));//利用unsafe對象來獲取threadLocalRandomSeed在內存地址的偏移量SEED = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomSeed"));//利用unsafe對象來獲取threadLocalRandomProbe在內存地址的偏移量 PROBE = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomProbe"));//利用unsafe對象來獲取threadLocalRandomSecondarySeed在內存地址的偏移量 SECONDARY = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomSecondarySeed"));} catch (Exception ex) { throw new Error(ex); }} }
由上面代碼可知這些變量是通過
static
代碼塊在類加載的時候就通過unsafe
對象獲取其在內存地址的偏移量了。?3.2 構造方法
public class LockSupport {//LockSupport只有一個私有構造函數,無法被實例化。private LockSupport() {} // Cannot be instantiated. }
3.3 兩個特殊的方法
public class LockSupport {//設置線程t的parkBlocker字段的值為argprivate static void setBlocker(Thread t, Object arg) {// Even though volatile, hotspot doesn't need a write barrier here.//盡管hotspot易變,但在這里并不需要寫屏障。UNSAFE.putObject(t, parkBlockerOffset, arg);}//獲取當前線程的Blocker值public static Object getBlocker(Thread t) {//若當前線程為空就拋出異常if (t == null)throw new NullPointerException();//利用unsafe對象獲取當前線程的Blocker值 return UNSAFE.getObjectVolatile(t, parkBlockerOffset);} }
1、
unpark(Thread thread)
方法public class LockSupport {//釋放該線程的阻塞狀態,即類似釋放鎖,只不過這里是將許可設置為1public static void unpark(Thread thread) {//判斷線程是否為空if (thread != null)//釋放該線程許可UNSAFE.unpark(thread);} }
2、
park(Object blocker)
方法 和?park()
方法public class LockSupport {//阻塞當前線程,并且將當前線程的parkBlocker字段設置為blockerpublic static void park(Object blocker) {//獲取當前線程Thread t = Thread.currentThread();//將當前線程的parkBlocker字段設置為blockersetBlocker(t, blocker);//阻塞當前線程,第一個參數表示isAbsolute,是否為絕對時間,第二個參數就是代表時間UNSAFE.park(false, 0L);//重新可運行后再此設置BlockersetBlocker(t, null);}//無限阻塞線程,直到有其他線程調用unpark方法public static void park() {UNSAFE.park(false, 0L);} }
說明:
-
調用
park
函數時,首先獲取當前線程,然后設置當前線程的parkBlocker
字段,即調用setBlocker
函數, 之后調用Unsafe
類的park
函數,之后再調用setBlocker
函數。?park(Object blocker)
函數中要調用兩次setBlocker
函數 -
1、調用
park
函數時,當前線程首先設置好parkBlocker
字段,然后再調用?Unsafe
的park函數,此時,當前線程就已經阻塞了,等待該線程的unpark
函數被調用,所以后面的一個?setBlocker
函數無法運行,unpark
函數被調用,該線程獲得許可后,就可以繼續運行了,也就運行第二個?setBlocker
,把該線程的parkBlocker
字段設置為null,這樣就完成了整個park
函數的邏輯。 - 2、如果沒有第二個?
setBlocker
,那么之后沒有調用park(Object blocker)
,而直接調用getBlocker
函數,得到的還是前一個?park(Object blocker)
設置的blocker
,顯然是不符合邏輯的。總之,必須要保證在park(Object blocker)
整個函數 執行完后,該線程的parkBlocker
字段又恢復為null。
所以,park(Object)
型函數里必須要調用setBlocker
函數兩次。?
3、parkNanos(Object blocker, long nanos)
方法 和?parkNanos(long nanos)
方法
public class LockSupport {//阻塞當前線程nanos秒public static void parkNanos(Object blocker, long nanos) {//先判斷nanos是否大于0,小于等于0都代表無限等待if (nanos > 0) {//獲取當前線程Thread t = Thread.currentThread();//將當前線程的parkBlocker字段設置為blockersetBlocker(t, blocker);//阻塞當前線程現對時間的nanos秒UNSAFE.park(false, nanos);//將當前線程的parkBlocker字段設置為nullsetBlocker(t, null);}} //阻塞當前線程nanos秒,現對時間public static void parkNanos(long nanos) {if (nanos > 0)UNSAFE.park(false, nanos);}
}
4、parkUntil(Object blocker, long deadline)
方法 和?parkUntil(long deadline)
方法
public class LockSupport {//將當前線程阻塞絕對時間的deadline秒,并且將當前線程的parkBlockerOffset設置為blockerpublic static void parkUntil(Object blocker, long deadline) {//獲取當前線程Thread t = Thread.currentThread();//設置當前線程parkBlocker字段設置為blockersetBlocker(t, blocker);//阻塞當前線程絕對時間的deadline秒UNSAFE.park(true, deadline);//當前線程parkBlocker字段設置為nullsetBlocker(t, null);}//將當前線程阻塞絕對時間的deadline秒public static void parkUntil(long deadline) {UNSAFE.park(true, deadline);}
}
總結:
LockSupport 和 CAS 是Java并發包中很多并發工具控制機制的基礎,它們底層其實都是依賴Unsafe實現。很多鎖的類都是基于LockSupport的park和unpark來實現的,所以了解LockSupport類是非常重要的。
如果小假的內容對你有幫助,請點贊,評論,收藏。創作不易,大家的支持就是我堅持下去的動力!