LockSupport:一個很靈活的線程工具類
LockSupport是一個編程工具類,主要是為了阻塞和喚醒線程用的。使用它我們可以實現(xiàn)很多功能,今天主要就是對這個工具類的講解,希望對你有幫助:
一、LockSupport簡介
1、LockSupport是什么
剛剛開頭提到過,LockSupport是一個線程工具類,所有的方法都是靜態(tài)方法,可以讓線程在任意位置阻塞,也可以在任意位置喚醒。
它的內(nèi)部其實兩類主要的方法:park(停車阻塞線程)和unpark(啟動喚醒線程)。
- //(1)阻塞當(dāng)前線程
- public static void park(Object blocker);
- //(2)暫停當(dāng)前線程,有超時時間
- public static void parkNanos(Object blocker, long nanos);
- //(3)暫停當(dāng)前線程,直到某個時間
- public static void parkUntil(Object blocker, long deadline);
- //(4)無期限暫停當(dāng)前線程
- public static void park();
- //(5)暫停當(dāng)前線程,不過有超時時間的限制
- public static void parkNanos(long nanos);
- //(6)暫停當(dāng)前線程,直到某個時間
- public static void parkUntil(long deadline);
- //(7)恢復(fù)當(dāng)前線程
- public static void unpark(Thread thread);
- public static Object getBlocker(Thread t);
注意上面的123方法,都有一個blocker,這個blocker是用來記錄線程被阻塞時被誰阻塞的。用于線程監(jiān)控和分析工具來定位原因的。
現(xiàn)在我們知道了LockSupport是用來阻塞和喚醒線程的,而且之前相信我們都知道wait/notify也是用來阻塞和喚醒線程的,那和它相比,LockSupport有什么優(yōu)點呢?
2、與wait/notify對比
這里假設(shè)你已經(jīng)了解了wait/notify的機制,如果不了解,可以在網(wǎng)上一搜,很簡單。相信你既然學(xué)到了這個LockSupport,相信你已經(jīng)提前已經(jīng)學(xué)了wait/notify。
我們先來舉一個使用案例:
- public class LockSupportTest {
- public static class MyThread extends Thread {
- @Override
- public void run() {
- System.out.println(getName() + " 進入線程");
- LockSupport.park();
- System.out.println("t1線程運行結(jié)束");
- }
- }
- public static void main(String[] args) {
- MyThread t1 = new MyThread();
- t1.start();
- System.out.println("t1已經(jīng)啟動,但是在內(nèi)部進行了park");
- LockSupport.unpark(t1);
- System.out.println("LockSupport進行了unpark");
- }
- }
上面這段代碼的意思是,我們定義一個線程,但是在內(nèi)部進行了park,因此需要unpark才能喚醒繼續(xù)執(zhí)行,不過上面,我們在MyThread進行的park,在main線程進行的unpark。
這樣來看,好像和wait/notify沒有什么區(qū)別。那他的區(qū)別到底是什么呢?這個就需要仔細的觀察了。這里主要有兩點:
(1)wait和notify都是Object中的方法,在調(diào)用這兩個方法前必須先獲得鎖對象,但是park不需要獲取某個對象的鎖就可以鎖住線程。
(2)notify只能隨機選擇一個線程喚醒,無法喚醒指定的線程,unpark卻可以喚醒一個指定的線程。
區(qū)別就是這倆,還是主要從park和unpark的角度來解釋的。既然這個LockSupport這么強,我們就深入一下他的源碼看看。
二、源碼分析(基于jdk1.8)
1、park方法
- public static void park(Object blocker) {
- Thread t = Thread.currentThread();
- setBlocker(t, blocker);
- UNSAFE.park(false, 0L);
- setBlocker(t, null);
- }
blocker是用來記錄線程被阻塞時被誰阻塞的。用于線程監(jiān)控和分析工具來定位原因的。setBlocker(t, blocker)方法的作用是記錄t線程是被broker阻塞的。因此我們只關(guān)注最核心的方法,也就是UNSAFE.park(false, 0L)。
UNSAFE是一個非常強大的類,他的的操作是基于底層的,也就是可以直接操作內(nèi)存,因此我們從JVM的角度來分析一下:
每個java線程都有一個Parker實例:
- class Parker : public os::PlatformParker {
- private:
- volatile int _counter ;
- ...
- public:
- void park(bool isAbsolute, jlong time);
- void unpark();
- ...
- }
- class PlatformParker : public CHeapObj<mtInternal> {
- protected:
- pthread_mutex_t _mutex [1] ;
- pthread_cond_t _cond [1] ;
- ...
- }
我們換一種角度來理解一下park和unpark,可以想一下,unpark其實就相當(dāng)于一個許可,告訴特定線程你可以停車,特定線程想要park停車的時候一看到有許可,就可以立馬停車?yán)^續(xù)運行了。因此其執(zhí)行順序可以顛倒。
現(xiàn)在有了這個概念,我們體會一下上面JVM層面park的方法,這里面counter字段,就是用來記錄所謂的“許可”的。
本小部分總結(jié)來源于:https://www.jianshu.com/p/1f16b838ccd8
當(dāng)調(diào)用park時,先嘗試直接能否直接拿到“許可”,即_counter>0時,如果成功,則把_counter設(shè)置為0,并返回。
- void Parker::park(bool isAbsolute, jlong time) {
- // Ideally we'd do something useful while spinning, such
- // as calling unpackTime().
- // Optional fast-path check:
- // Return immediately if a permit is available.
- // We depend on Atomic::xchg() having full barrier semantics
- // since we are doing a lock-free update to _counter.
- if (Atomic::xchg(0, &_counter) > 0) return;
如果不成功,則構(gòu)造一個ThreadBlockInVM,然后檢查_counter是不是>0,如果是,則把_counter設(shè)置為0,unlock mutex并返回:
- ThreadBlockInVM tbivm(jt);
- // no wait needed
- if (_counter > 0) {
- _counter = 0;
- status = pthread_mutex_unlock(_mutex);
否則,再判斷等待的時間,然后再調(diào)用pthread_cond_wait函數(shù)等待,如果等待返回,則把_counter設(shè)置為0,unlock mutex并返回:
- if (time == 0) {
- status = pthread_cond_wait (_cond, _mutex) ;
- }
- _counter = 0 ;
- status = pthread_mutex_unlock(_mutex) ;
- assert_status(status == 0, status, "invariant") ;
- OrderAccess::fence();
這就是整個park的過程,總結(jié)來說就是消耗“許可”的過程。
2、unpark
還是先來看一下JDK源碼:
- /**
- * Makes available the permit for the given thread, if it
- * was not already available. If the thread was blocked on
- * {@code park} then it will unblock. Otherwise, its next call
- * to {@code park} is guaranteed not to block. This operation
- * is not guaranteed to have any effect at all if the given
- * thread has not been started.
- *
- * @param thread the thread to unpark, or {@code null}, in which case
- * this operation has no effect
- */
- public static void unpark(Thread thread) {
- if (thread != null)
- UNSAFE.unpark(thread);
- }
上面注釋的意思是給線程生產(chǎn)許可證。
當(dāng)unpark時,則簡單多了,直接設(shè)置_counter為1,再unlock mutext返回。如果_counter之前的值是0,則還要調(diào)用pthread_cond_signal喚醒在park中等待的線程:
- void Parker::unpark() {
- int s, status ;
- status = pthread_mutex_lock(_mutex);
- assert (status == 0, "invariant") ;
- s = _counter;
- _counter = 1;
- if (s < 1) {
- if (WorkAroundNPTLTimedWaitHang) {
- status = pthread_cond_signal (_cond) ;
- assert (status == 0, "invariant") ;
- status = pthread_mutex_unlock(_mutex);
- assert (status == 0, "invariant") ;
- } else {
- status = pthread_mutex_unlock(_mutex);
- assert (status == 0, "invariant") ;
- status = pthread_cond_signal (_cond) ;
- assert (status == 0, "invariant") ;
- }
- } else {
- pthread_mutex_unlock(_mutex);
- assert (status == 0, "invariant") ;
- }
- }
ok,現(xiàn)在我們已經(jīng)對源碼進行了分析,整個過程其實就是生產(chǎn)許可和消費許可的過程。而且這個生產(chǎn)過程可以反過來。也就是先生產(chǎn)再消費。下面我們使用幾個例子驗證一波。
三、LockSupport使用
1、先interrupt再park
- public class LockSupportTest {
- public static class MyThread extends Thread {
- @Override
- public void run() {
- System.out.println(getName() + " 進入線程");
- LockSupport.park();
- System.out.println(" 運行結(jié)束");
- System.out.println("是否中斷:" + Thread.currentThread().isInterrupted());
- }
- }
- public static void main(String[] args) {
- MyThread t1 = new MyThread();
- t1.start();
- System.out.println("t1線程已經(jīng)啟動了,但是在內(nèi)部LockSupport進行了park");
- t1.interrupt();
- System.out.println("main線程結(jié)束");
- }
- }
我們看一下結(jié)果:
2、先unpark再park
- public static class MyThread extends Thread {
- @Override
- public void run() {
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(getName() + " 進入線程");
- LockSupport.park();
- System.out.println(" 運行結(jié)束");
- }
- }
我們只需在park之前先休眠1秒鐘,這樣可以確保unpark先執(zhí)行。
本文轉(zhuǎn)載自微信公眾號「愚公要移山」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系愚公要移山公眾號。