最初想有沒有必要寫這類文章,網上相關的文章很多,有些更為透徹,自己再寫一篇不免有重復造輪子的感覺。
但想想寫文除了分享知識外也可以幫助自己總結歸納,也稍稍可以提高點自我滿足感。
基本的線程阻塞原語,被用於創建鎖和其他同步類上。
這個類的作用有點類似於Semaphore
,通過許可證(permit
)來聯系使用它的線程。如果許可證可用,調用park
方法會立即返回並在這個過程中消費這個許可,不然線程會阻塞。調用unpark
會使許可證可用。(和Semaphores
有些許區別,許可證不會累加,最多只有一張)
因為有了許可證,所以調用park
和unpark
的先后關系就不重要了,這里可以對比一下Object的wait
和notify
,如果先調用同一個對象的notify
再wait
,那么調用wait
的線程依舊會被阻塞,依賴方法的調用順序。在一些場景下,LockSupport
的方法都可以取代notify
和wait
。舉個簡單的例子:
final Object lock = new Object();
Thread t1 = new Thread() {
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
lock.notifyAll();
}
};
};
Thread t2 = new Thread() {
public void run() {
synchronized (lock) {
try {
lock.wait();
System.out.println("I am alive");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
};
t1.start();
t2.start();
這段代碼,只有確保t2先進入臨界區並執行wait后才能正常打印內容。
換成LockSupport
就沒有這種擔心了。
Thread t2 = new Thread() {
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 end sleep");
LockSupport.park(this);
System.out.println("I am alive");
};
};
Thread t1 = new Thread() {
public void run() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(t2);
System.out.println("I am done");
};
};
t1.start();
t2.start();
可以先unpark
再park
,但如API文檔所說,unpark
需要傳入的線程已經啟動。在上述t1.start()
后用t1.join()
等方法在t2
啟動前調用了unpark
就無效了。
但也不意味着wait
,notify
就沒用了。wait
是調用對象的方法,和這個對象資源綁定,線程間不需要彼此感知到對方(能獲取對方引用),只需要共有這個資源即可,另一方面LockSupport
可以指定unpark
的線程,這點也是前者無法做到的,兩者間不能完全取代彼此。
park
方法也有可能在沒有"任何理由"的情況下返回,所以通常要在一個循環中使用它並重新判斷可以返回的條件。這一點和Object.wait
方法一樣,可以參考wiki,大致可以理解為底層系統因為優化需要允許出現這樣的情況,JVM的線程就是系統線程這種情況也無法避免。可以作為"忙等待"的優化,但是必須要注意要有配對的unpark方法。
使用模式如下:
while (!canProceed()) { ... LockSupport.park(this); }}
在提供的方法中可以看到有一組傳入名為blocker對象作為參數的方法,記錄這個對象可以方便許可證監視和診斷工具分析。
取上述代碼,不啟動t1的情況直接運行t2進入阻塞,jstack可以看到:
"Thread-0" #10 prio=5 os_prio=0 tid=0x000000001b586800 nid=0x37f0 waiting on condition [0x000000001c0ae000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000d603a6e8> (a test.TestLockSupport$1)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at test.TestLockSupport$1.run(TestLockSupport.java:21)
會將傳入的對象打出。
更多資料:
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/LockSupport.html