• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • LockSupport源碼詳細分析

    一. LockSupport類介紹

    前面分析中,阻塞和喚醒線程都會使用到LockSupport工具來完成相應工作,LockSupport定義了一組公共靜態方法,這些方法提供了最基本的線程阻塞和喚醒公共,而LockSupport也成為構建同步組件的基礎工具。

     

    LockSupport定義了一組以park開頭的方法用來阻塞當前線程,以及upark方法用來喚醒線程。這些方法如下所示:

    方法名稱 描述
    void park() 阻塞當前線程,如果調用unpark(Thread)或者當前線程被中斷,才能從park()方法返回
    void parkNanos(long nanos) 阻塞當前線程,最長不超過nanos納秒,返回條件在park()的基礎上增加了超時返回
    void parkUntil(long deadline) 阻塞當前線程,直到deadline
    void unpark(Thread thread) 喚醒處于阻塞的線程thread

    三種形式的 park 還各自支持一個 blocker 對象參數。此對象在線程受阻塞時被記錄,以允許監視工具和診斷工具確定線程受阻塞的原因。(這樣的工具可以使用方法 getBlocker(java.lang.Thread) 訪問 blocker。)建議最好使用這些形式,而不是不帶此參數的原始形式。在鎖實現中提供的作為 blocker 的普通參數是 this。
    看下線程dump的結果來理解blocker的作用。

    從線程dump結果可以看出:
    有blocker的可以傳遞給開發人員更多的現場信息,通過jstack命令可以非常方便的監控具體的阻塞對象,方便定位問題。所以java6新增加帶blocker入參的系列park方法,替代原有的park方法。

     

    Unsafe的park和unpark

    LockSupport類是Java6(JSR166-JUC)引入的一個類,提供了基本的線程同步原語。LockSupport實際上是調用了Unsafe類里的函數,歸結到Unsafe里,只有兩個函數:

    /**
     * 為指定線程提供“許可(permit)”
     */
    public native void unpark(Thread jthread);
    
    /**
     * 阻塞指定時間等待“許可”。
     * @param isAbsolute: 時間是絕對的,還是相對的
     * @param time:等待許可的時間
     */
    public native void park(boolean isAbsolute, long time);  

    上面的這個“許可”是不能疊加的,“許可”是一次性的。

    比如線程B連續調用了三次unpark函數,當線程A調用park函數就使用掉這個“許可”,如果線程A再次調用park,則進入等待狀態。

    注意,unpark函數可以先于park調用。比如線程B調用unpark函數,給線程A發了一個“許可”,那么當線程A調用park時,它發現已經有“許可”了,那么它會馬上再繼續運行。

    可能有些朋友還是不理解“許可”這個概念,我們深入HotSpot的源碼來看看。

    每個java線程都有一個Parker實例,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] ;  
        ...  
    }  

    可以看到Parker類實際上用Posix的mutex,condition來實現的。在Parker類里的_counter字段,就是用來記錄所謂的“許可”的。

    當調用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) > 0) return;  
    

    如果不成功,則構造一個ThreadBlockInVM,然后檢查_counter是不是>0,如果是,則把_counter設置為0,unlock mutex并返回:

    ThreadBlockInVM tbivm(jt);  
    if (_counter > 0)  { // no wait needed  
      _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();  
    

    當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") ;  
      }  
    }  
    

    簡而言之,是用mutex和condition保護了一個_counter的變量,當park時,這個變量置為了0,當unpark時,這個變量置為1。

    值得注意的是在park函數里,調用pthread_cond_wait時,并沒有用while來判斷,所以posix condition里的"Spurious wakeup"一樣會傳遞到上層Java的代碼里。關于"Spurious wakeup",可以參考:并行編程之條件變量(posix condition variables)

    3、LockSupport源碼分析

    解釋完Unsafe的park和unpark的實現原理,我們再來看LockSupport的源碼時就會異常清晰,因為不復雜,所以直接看注釋吧。

    public class LockSupport {
        private LockSupport() {} // Cannot be instantiated.
    
        private static void setBlocker(Thread t, Object arg) {
            UNSAFE.putObject(t, parkBlockerOffset, arg);
        }
        
        /**
         * 返回提供給最近一次尚未解除阻塞的 park 方法調用的 blocker 對象。
         * 如果該調用不受阻塞,則返回 null。
         * 返回的值只是一個瞬間快照,即由于未解除阻塞或者在不同的 blocker 對象上受阻而具有的線程。
         */
        public static Object getBlocker(Thread t) {
            if (t == null)
                throw new NullPointerException();
            return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
        }
        
        /**
         * 如果給定線程的許可尚不可用,則使其可用。
         * 如果線程在 park 上受阻塞,則它將解除其阻塞狀態。
         * 否則,保證下一次調用 park 不會受阻塞。
         * 如果給定線程尚未啟動,則無法保證此操作有任何效果。 
         * @param thread: 要執行 unpark 操作的線程;該參數為 null 表示此操作沒有任何效果。
         */
        public static void unpark(Thread thread) {
            if (thread != null)
                UNSAFE.unpark(thread);
        }
    
        /**
         * 為了線程調度,在許可可用之前阻塞當前線程。 
         * 如果許可可用,則使用該許可,并且該調用立即返回;
         * 否則,為線程調度禁用當前線程,并在發生以下三種情況之一以前,使其處于休眠狀態:
         *  1. 其他某個線程將當前線程作為目標調用 unpark
         *  2. 其他某個線程中斷當前線程
         *  3. 該調用不合邏輯地(即毫無理由地)返回
         */
        public static void park() {
            UNSAFE.park(false, 0L);
        }
    
        /**
         * 和park()方法類似,不過增加了等待的相對時間
         */
        public static void parkNanos(long nanos) {
            if (nanos > 0)
                UNSAFE.park(false, nanos);
        }
    
        /**
         * 和park()方法類似,不過增加了等待的絕對時間
         */
        public static void parkUntil(long deadline) {
            UNSAFE.park(true, deadline);
        }
        
        /**
         * 和park()方法類似,只不過增加了暫停的同步對象
         * @param blocker 導致此線程暫停的同步對象
         * @since 1.6
         */
        public static void park(Object blocker) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(false, 0L);
            setBlocker(t, null);
        }
        
        /**
         * parkNanos(long nanos)方法類似,只不過增加了暫停的同步對象
         * @param blocker 導致此線程暫停的同步對象
         * @since 1.6
         */
        public static void parkNanos(Object blocker, long nanos) {
            if (nanos > 0) {
                Thread t = Thread.currentThread();
                setBlocker(t, blocker);
                UNSAFE.park(false, nanos);
                setBlocker(t, null);
            }
        }
        
        /**
         * parkUntil(long deadline)方法類似,只不過增加了暫停的同步對象
         * @param blocker 導致此線程暫停的同步對象
         * @since 1.6
         */
        public static void parkUntil(Object blocker, long deadline) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(true, deadline);
            setBlocker(t, null);
        }
    
        static final int nextSecondarySeed() {
            int r;
            Thread t = Thread.currentThread();
            if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
                r ^= r << 13;   // xorshift
                r ^= r >>> 17;
                r ^= r << 5;
            }
            else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)
                r = 1; // avoid zero
            UNSAFE.putInt(t, SECONDARY, r);
            return r;
        }
    
        // Hotspot implementation via intrinsics API
        private static final sun.misc.Unsafe UNSAFE;
        private static final long parkBlockerOffset;
        private static final long SEED;
        private static final long PROBE;
        private static final long SECONDARY;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> tk = Thread.class;
                parkBlockerOffset = UNSAFE.objectFieldOffset
                    (tk.getDeclaredField("parkBlocker"));
                SEED = UNSAFE.objectFieldOffset
                    (tk.getDeclaredField("threadLocalRandomSeed"));
                PROBE = UNSAFE.objectFieldOffset
                    (tk.getDeclaredField("threadLocalRandomProbe"));
                SECONDARY = UNSAFE.objectFieldOffset
                    (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
            } catch (Exception ex) { throw new Error(ex); }
        }
    }
    

    4、例子

    看完LockSupport的源碼,我們來動手寫幾個例子來驗證一下猜想是否正確。

    4.1、先park再unpark

    public class LockSupportTest {
    
        public static void main(String[] args) throws InterruptedException {
            String a = new String("A");
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("睡覺");
                    LockSupport.park(a);
                    System.out.println("起床");
                }
            });
            t.setName("A-Name");
            t.start();
            Thread.sleep(300000);
            System.out.println("媽媽喊我起床");
            LockSupport.unpark(t);
        }
    }
    

    輸出結果:

    睡覺
    媽媽喊我起床
    起床
    

    不過在等待的過程中,我們可以用jstack查看是否能夠打印出檢測的對象A,找到A-Name這個線程確實看到了等待一個String對象

    ~ jps
    5589 LockSupportTest
    
    ~ jstack 5589
    "A-Name" #11 prio=5 os_prio=31 tid=0x00007fc143009800 nid=0xa803 waiting on condition [0x000070000c233000]
       java.lang.Thread.State: WAITING (parking)
            at sun.misc.Unsafe.park(Native Method)
            - parking to wait for  <0x000000076adf4d30> (a java.lang.String)
            at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
            at com.github.locksupport.LockSupportTest$1.run(LockSupportTest.java:18)
            at java.lang.Thread.run(Thread.java:745)
    

    驗證完unpark,接著我們來驗證一下interrupt。

    4.2、先interrupt再park

    public class LockSupportTest {
    
        public static void main(String[] args) throws InterruptedException {
            String a = new String("A");
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("睡覺");
                    LockSupport.park(a);
                    System.out.println("起床");
                    System.out.println("是否中斷:" + Thread.currentThread().isInterrupted());
                }
            });
            t.setName("A-Name");
            t.start();
            t.interrupt();
            System.out.println("突然肚子很疼");
        }
    }
    

    可以看到中斷后執行park會直接執行下面的方法,并不會拋出InterruptedException,輸出結果如下:

    突然肚子很疼
    睡覺
    起床
    是否中斷:true
    

    4.3、先unpark再park

    public class LockSupportTest {
    
        public static void main(String[] args) throws InterruptedException {
            String a = new String("A");
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("睡覺");
                    LockSupport.park(a);
                    System.out.println("7點到,起床");
                }
            });
            t.setName("A-Name");
            t.start();
            LockSupport.unpark(t);
            System.out.println("提前上好鬧鐘7點起床");
        }
    }
    

    按照上面說過的,先設置好許可(unpark)再獲取許可的時候不會進行等待,正如我們說的那樣輸出如下:

    提前上好鬧鐘7點起床
    睡覺
    7點到,起床
    

     

    線程一共有六種狀態,而park系列方法線程進入兩種狀態:WAITING等待狀態或TIMED_WAITING等待狀態。這兩種狀態都會使線程阻塞在當前位置。
    那么怎么喚醒這兩種狀態的線程呢?

    對于WAITING等待狀態有兩種喚醒方式:

    1. 調用對應的喚醒方法。這里就是LockSupport的unpark方法。
    2. 調用該線程變量的interrupt()方法,會喚醒該線程,并拋出InterruptedException異常。

    對于TIMED_WAITING等待狀態來說,它比WAITING狀態多了一種喚醒方式,就是超過規定時間,那么線程會自動醒來。

     

    思考一個問題

    看完源碼后,是不是覺得LockSupport.park()和unpark()和object.wait()和notify()很相似,那么它們有什么區別呢?

    1. 面向的主體不一樣。LockSuport主要是針對Thread進進行阻塞處理,可以指定阻塞隊列的目標對象,每次可以指定具體的線程喚醒。Object.wait()是以對象為緯度,阻塞當前的線程和喚醒單個(隨機)或者所有線程。
    2. 實現機制不同。雖然LockSuport可以指定monitor的object對象,但和object.wait(),兩者的阻塞隊列并不交叉。可以看下測試例子。object.notifyAll()不能喚醒LockSupport的阻塞Thread.

     

    對很少部分做整理,自己梳理一遍代碼思路更加清晰,膜拜原作者!侵刪!

    本文參考:

                                     https://www.jianshu.com/p/4d19684917d2

                                     https://www.jianshu.com/p/1f16b838ccd8

    版權聲明:本文為UckyK原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/UckyK/article/details/103349304

    智能推薦

    HBase 0.92.1 Scan 源碼詳細分析

    從這篇文章開始終于要討論比較正常版本的hbase了---0.92.1~~ Scan是hbase提供的非常重要的功能之一,我們的hbase分析系列就從這兒開始吧。 首先,我們有一些background的知識需要了解: 1.HBase是如何區分不同記錄的,大家可以參考http://punishzhou.iteye.com/blog/1266341,講的比較詳細 2.Region,MemStore,St...

    Netty5.0的NioEventLoop源碼詳細分析

    了解Netty線程模型的小伙伴應該都知道,Netty的線程有兩個NioEventLoopGroup線程池,一個是boss線程池,一個是worker線程池,其中worker線程池的任務如下: a.異步讀取通訊對端的消息,向ChannelPipeline發出讀事件 b.異步向通訊對端發送消息,調用ChannelPipeline發送消息接口 c.執行系統Task任務 d.執行定時任務  系統T...

    [詳細分析]Java-ArrayList源碼全解析

    目錄 類圖 屬性 構造方法 帶初始容量的構造方法 無參構造方法 帶一個集合參數的構造方法 插入方法 在列表最后添加指定元素 在指定位置添加指定元素 插入方法調用的其他私有方法 擴容方法 移除方法 移除指定下標元素方法 移除指定元素方法 私有移除方法 查找方法 查找指定元素的所在位置 查找指定位置的元素 序列化方法 反序列化方法 創建子數組 迭代器 創建迭代器方法 Itr屬性 Itr的hasNext...

    JetPack之ViewModel最新源碼詳細分析

    本文會基于最新版ViewModel使用方法與源碼進行詳細分析,從注冊到實現ViewModel界面數據如何保存與管理全部涉及。 ** 簡介: **ViewModel是JetPack系列庫之一,它用來對組件的界面數據進行管理,且當組件的狀態發生改變時數據依然留存。 優點:1.當所依賴組件的狀態發生改變時,例如屏幕旋轉等,界面數據不會發生改變 2.實現MVVM架構的基礎,在日常開發中,ViewModel...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    猜你喜歡

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    精品国产乱码久久久久久蜜桃不卡