1 引言
上一篇簡單介紹了Java中的Thread機制,附傳送門↓:
Java併發編程(一):瞭解Thread
我們知道,可以通過wait()
讓線程等待,通過notify()
喚醒線程,但使用wait()
,notify()
來實現等待喚醒功能至少有兩個缺點:
-
wait()
和notify()
都是Object
中的方法,在調用這兩個方法前必須先獲得鎖對象,這限制了其使用場合:只能在同步代碼塊中。 -
另一個缺點是當對象的等待隊列中有多個線程時,
notify()
只能隨機選擇一個線程喚醒,無法喚醒指定的線程。
而使用LockSupport
的話,我們可以在任何場合使線程阻塞,同時也可以指定要喚醒的線程,較爲方便。
2 LockSupport的應用
首先列舉LockSupport
常用的幾個方法:
// 暫停當前線程
public static void park(Object blocker);
// 暫停當前線程,不過有超時時間的限制
public static void parkNanos(Object blocker, long nanos);
// 暫停當前線程,直到某個時間
public static void parkUntil(Object blocker, long deadline);
// 無期限暫停當前線程
public static void park();
// 暫停當前線程,不過有超時時間的限制
public static void parkNanos(long nanos);
// 暫停當前線程,直到某個時間
public static void parkUntil(long deadline);
// 恢復指定線程的運行
public static void unpark(Thread thread);
public static Object getBlocker(Thread t);
方法入參的
Object blocker
是代表什麼呢?這其實就是方便在線程dump的時候看到具體的阻塞對象的信息。
再通過一個小栗子簡單演示LockSupport
的用法:
/**
* @author Carson Chu, [email protected]
* @date 2020/4/4 10:41
* @description
*/
public class Main {
public static void main(String[] args) {
FutureTask<Boolean> futureTask = new FutureTask<>(() -> {
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return true;
});
Thread thread1 = new Thread(futureTask, "thread-1");
Thread thread2 = new Thread(() -> {
for (int i = 2; i < 4; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
/* 讓線程等待 */
LockSupport.park();
}
}, "thread-2");
thread1.start();
thread2.start();
LockSupport.unpart(thread2);
}
}
由上述代碼可以看出,線程2在調度的時候,進入循環第一次輸出數字時,調用的LockSupport.park()
方法,所以線程2只會輸出一個數字然後進入等待狀態。當然最後不要忘記調用LockSupport.unpark()
喚醒。
在瞭解LockSupport
的等待喚醒的基本操作之後,再來看下面一段代碼:
public class Main {
public static void main(String[] args) {
FutureTask<Boolean> futureTask = new FutureTask<>(() -> {
Thread currentThread = Thread.currentThread();
for (int i = 0; i < 2; i++) {
System.out.println(currentThread.getName() + ":" + i);
/* 喚醒線程 */
LockSupport.unpark(currentThread);
}
/* 讓線程等待 */
LockSupport.park();
return true;
});
Thread thread1 = new Thread(futureTask, "thread-1");
thread1.start();
}
}
上述代碼關鍵在於喚醒操作執行在了等待操作之前,那麼線程是否會發生死鎖呢?
答案是不會,這點不同於stop
和resume
機制,stop
和resume
如果順序反了,會出現死鎖現象。
那麼是什麼原因呢?
讓我們來結合源碼分析。
首先貼出park()
的實現原理:
public static void park() {
/* 該方法是本地方法native */
UNSAFE.park(false, 0L);
}
再貼出unpark()
的實現原理:
public static void unpark(Thread thread) {
if (thread != null)
/* 該方法是本地方法native */
UNSAFE.unpark(thread);
}
觀察上述源碼,尤其是park()
,眼光銳利如你,相信一定看到了false
這個關鍵字,沒錯,這就是重點。實際上LockSupport
底層維護了一個許可(permit
),即上述源碼裏的布爾值。那麼它是什麼原理呢?一言以蔽之就是:
park()
調用時,判斷許可是否爲true,如果是true,則繼續往下執行;如果是false,則調用線程等待,直到許可爲true。當unpark()
調用時,如果當前線程還未進入park()
,則許可爲true。
再結合上述樣例代碼,我先調用unpark()
方法,此時線程還有進入park()
,所以許可爲true
,之後再調用park()
方法時,判斷許可值爲true
,則線程不會執行等待操作,而是繼續執行,因而不會發生死鎖。
小結
park()
和unpark()
可以實現類似wait()
和notify()
的功能,但是並不和wait()
和notify()
交叉,也就是說unpark()
不會對wait()
起作用,notify()
也不會對park()
起作用。park()
和unpark()
的使用不會出現死鎖的情況。