LockSupport:一個很靈活的線程工具類

LockSupport是一個編程工具類,主要是爲了阻塞和喚醒線程用的。使用它咱們能夠實現不少功能,今天主要就是對這個工具類的講解,但願對你有幫助:java

1、LockSupport簡介web




一、LockSupport是什麼

剛剛開頭提到過,LockSupport是一個線程工具類,全部的方法都是靜態方法,可讓線程在任意位置阻塞,也能夠在任意位置喚醒。編程

它的內部其實兩類主要的方法:park(停車阻塞線程)和unpark(啓動喚醒線程)。微信

//(1)阻塞當前線程
public static void park(Object blocker)
//(2)暫停當前線程,有超時時間
public static void parkNanos(Object blocker, long nanos)
//(3)暫停當前線程,直到某個時間
public static void parkUntil(Object blocker, long deadline)
//(4)無期限暫停當前線程
public static void park()
//(5)暫停當前線程,不過有超時時間的限制
public static void parkNanos(long nanos)
//(6)暫停當前線程,直到某個時間
public static void parkUntil(long deadline);  
//(7)恢復當前線程
public static void unpark(Thread thread)
public static Object getBlocker(Thread t);

注意上面的123方法,都有一個blocker,這個blocker是用來記錄線程被阻塞時被誰阻塞的。用於線程監控和分析工具來定位緣由的。app

如今咱們知道了LockSupport是用來阻塞和喚醒線程的,並且以前相信咱們都知道wait/notify也是用來阻塞和喚醒線程的,那和它相比,LockSupport有什麼優勢呢?less

二、與wait/notify對比

這裏假設你已經瞭解了wait/notify的機制,若是不瞭解,能夠在網上一搜,很簡單。相信你既然學到了這個LockSupport,相信你已經提早已經學了wait/notify。編輯器

咱們先來舉一個使用案例:ide

public class LockSupportTest {
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println(getName() + " 進入線程");
            LockSupport.park();
            System.out.println("t1線程運行結束");
        }
    }
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
        System.out.println("t1已經啓動,可是在內部進行了park");
        LockSupport.unpark(t1);
        System.out.println("LockSupport進行了unpark");
    }
}

上面這段代碼的意思是,咱們定義一個線程,可是在內部進行了park,所以須要unpark才能喚醒繼續執行,不過上面,咱們在MyThread進行的park,在main線程進行的unpark。函數

這樣來看,好像和wait/notify沒有什麼區別。那他的區別究竟是什麼呢?這個就須要仔細的觀察了。這裏主要有兩點:工具

(1)wait和notify都是Object中的方法,在調用這兩個方法前必須先得到鎖對象,可是park不須要獲取某個對象的鎖就能夠鎖住線程。

(2)notify只能隨機選擇一個線程喚醒,沒法喚醒指定的線程,unpark卻能夠喚醒一個指定的線程。

區別就是這倆,仍是主要從park和unpark的角度來解釋的。既然這個LockSupport這麼強,咱們就深刻一下他的源碼看看。

2、源碼分析(基於jdk1.8)




一、park方法

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false0L);
        setBlocker(t, null);
    }

blocker是用來記錄線程被阻塞時被誰阻塞的。用於線程監控和分析工具來定位緣由的。setBlocker(t, blocker)方法的做用是記錄t線程是被broker阻塞的。所以咱們只關注最核心的方法,也就是UNSAFE.park(false, 0L)。

UNSAFE是一個很是強大的類,他的的操做是基於底層的,也就是能夠直接操做內存,所以咱們從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其實就至關於一個許可,告訴特定線程你能夠停車,特定線程想要park停車的時候一看到有許可,就能夠立馬停車繼續運行了。所以其執行順序能夠顛倒。

如今有了這個概念,咱們體會一下上面JVM層面park的方法,這裏面counter字段,就是用來記錄所謂的「許可」的。

本小部分總結來源於:https://www.jianshu.com/p/1f16b838ccd8

當調用park時,先嚐試直接可否直接拿到「許可」,即_counter>0時,若是成功,則把_counter設置爲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) > 0return;

若是不成功,則構造一個ThreadBlockInVM,而後檢查_counter是否是>0,若是是,則把_counter設置爲0,unlock mutex並返回:

  ThreadBlockInVM tbivm(jt);
  // no wait needed
  if (_counter > 0)  { 
    _counter = 0;
    status = pthread_mutex_unlock(_mutex);

不然,再判斷等待的時間,而後再調用pthread_cond_wait函數等待,若是等待返回,則把_counter設置爲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的過程,總結來講就是消耗「許可」的過程。

二、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);
    }

上面註釋的意思是給線程生產許可證。

當unpark時,則簡單多了,直接設置_counter爲1,再unlock mutext返回。若是_counter以前的值是0,則還要調用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,如今咱們已經對源碼進行了分析,整個過程其實就是生產許可和消費許可的過程。並且這個生產過程能夠反過來。也就是先生產再消費。下面咱們使用幾個例子驗證一波。

3、LockSupport使用



一、先interrupt再park

public class LockSupportTest {
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println(getName() + " 進入線程");
            LockSupport.park();
            System.out.println(" 運行結束");
            System.out.println("是否中斷:" + Thread.currentThread().isInterrupted());
        }
    }
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
        System.out.println("t1線程已經啓動了,可是在內部LockSupport進行了park");
        t1.interrupt();
        System.out.println("main線程結束");
    }
}

咱們看一下結果:


二、先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(" 運行結束");
        }
    }

咱們只需在park以前先休眠1秒鐘,這樣能夠確保unpark先執行。

OK,今天的文章先寫到這,若有問題,還請批評指正。

用鞭子抽着,陀螺纔會旋轉。我是愚公,要移山。



本文分享自微信公衆號 - 愚公要移山(fdd_sxu_nwpu)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索