LockSupport最全使用方法 原理介紹

原理

    官方說是線程阻塞的基本原語,可以用來創建鎖或者其它的同步類(ReentrantLock基於該類實現)。每個線程使用LockSupport都會關聯一個 “許可證”。

    某個線程調用LockSupport.park,如果對應的“許可證”可用,則此次調用立即返回,否則線程將會阻塞直到中斷髮生、超時或者許可證狀態變爲可用。LockSupport.unpark(thread)可以使線程的許可證可用,但是unpark不能累積,只能使許可證從不可用到可用,不能累加可用次數。也就是說連續調用unpark和調用一次unpark效果是一樣的。

public class LockSupport {
     /**
     * 給某個線程頒發許可證,喚醒線程
     */
    public static void unpark(Thread thread);
     /**
     * 阻塞調用線程直到線程中斷或者許可證狀態變爲可用,如果許可證可用(之前調用過一次unpark)則立 
     * 即返回
     * blocker是設置阻塞線程的對象,追蹤問題用吧
     */    
    public static void park(Object blocker);
     /**
     * 阻塞調用線程直到線程中斷、等待nanos納秒或許可證狀態變爲可用,如果許可證可用(之前調用過一 
     *次unpark)則立即返回
     * blocker是設置阻塞線程的對象,追蹤問題用吧
     */
    public static void parkNanos(Object blocker, long nanos);
     /**
     * 阻塞調用線程直到線程中斷、到來截止時間(毫秒)或者許可證狀態變爲可用,如果許可證可用(之前調            
     * 用過一次unpark)則立即返回
     * blocker是設置阻塞線程的對象,追蹤問題用吧
     */
    public static void parkUntil(Object blocker, long deadline);
     /**
     * 阻塞調用線程直到線程中斷或者許可證狀態變爲可用,如果許可證可用(之前調用過一次unpark)則立 
     * 即返回
     */
    public static void park();
     /**
     * 阻塞調用線程直到線程中斷、等待nanos納秒或許可證狀態變爲可用,如果許可證可用(之前調用過一 
     *次unpark)則立即返回
     */
    public static void parkNanos(long nanos);
     /**
     * 阻塞調用線程直到線程中斷、到來截止時間(毫秒)或者許可證狀態變爲可用,如果許可證可用(之前調            
     * 用過一次unpark)則立即返回
     */
    public static void parkUntil(long deadline);
}

     park底層調用的是Unsafe類的park方法


public final class Unsafe {
    /**
    * isAbsolute表示絕對時間時間還是相對時間
    * time 時間,isAbsolute爲true就是毫秒時間戳,絕對時間;isAbsolute爲false就是納秒相對時間,多    
    * 少納秒以後醒來
    **/
    public native void park(boolean isAbsolute, long time);
}

    LockSupport的效果跟Thread.sleep類似,但是它跟線程sleep有以下不同:

  1. sleep和park都阻塞當前線程的執行,兩者都不釋放佔有的鎖資源
  2. sleep需要指定時間,park可指定也可不指定;
  3. sleep不能被其它線程喚醒,park可以被其它線程喚醒;
  4. sleep需要捕獲InterruptedException異常,park不需要;
  5. park如果想知道喚醒的原因是否是InterruptedException,需要去判斷線程的中斷狀態Thread.interrupted()。

 

應用舉例

    jdk的很多同步操作是基於LockSupport做的,例如FutureTask的get操作(阻塞獲取Future的結果),當任務狀態沒有完成時線程調用LockSupport.park,並將作爲FutrueTask對象的等待節點放在waiters鏈表裏面。源碼如下所示(關鍵部分筆者添加了註釋):

public class FutureTask<V> implements RunnableFuture<V> {
    ......
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        // 
        //筆者注:jdk很多源碼都使用這種循環的風格,每次循環只執行一個操作。例如創建一個等待節點入 
        //waiters鏈,分兩次循環,第一次創建節點,第二次循環入waiters鏈。筆者認爲這樣做的好處是:
        //該任務可能在“創建節點”和“入waiters鏈”中間完成,這樣第二次循環就不是入waiters鏈而是直 
        //接返回。其它情況也是一樣,任務的完成是異步的,隨時可能在某個語句之後完成。這樣寫能讓線 
        //程儘快返回。
        //
        for (;;) {
            //
            //筆者注:喚醒線程的原因如果是中斷,則要拋中斷異常 並且將當前線程從waiters鏈中移除
            //
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            //
            //筆者注:本次循環中如果任務狀態已完成,則直接返回
            //
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            else if (q == null)
            // 
            //筆者注:創建一個等待節點,該節點會保存當前線程對象
            //
                q = new WaitNode();
            else if (!queued)
            // 
            //筆者注:如果等待節點沒有入隊,則入waiters鏈
            //
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                //
                //筆者注:如果設置了等待時間,且等待時間未到,則parkNanos阻塞線程一定的時間
                //
                LockSupport.parkNanos(this, nanos);
            }
            else
                //
                //筆者注:阻塞線程
                //
                LockSupport.park(this);
        }
    }
    ......
}

   當任務完成,執行線程會遍歷waiters鏈表,並喚起每個waiter對應的線程。源碼如下所示(關鍵部分筆者添加了註釋):

public class FutureTask<V> implements RunnableFuture<V> {
    ......
    private void finishCompletion() {
        // assert state > COMPLETING;
        //
        //筆者注:利用自旋搶到執行權,然後去遍歷waiters鏈表
        //
        for (WaitNode q; (q = waiters) != null;) {
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                //
                //筆者注:遍歷waiters鏈表,喚醒對應等待該任務的線程
                //
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        //
                        //筆者注:此處喚醒等待線程
                        //
                        LockSupport.unpark(t);
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }

        done();

        callable = null;        // to reduce footprint
    }
    ......
}

Demo

LockSuport各方法場景實驗,demo代碼如下:

public class LockSupportDemo {
    private static Thread threadA1 = new Thread() {
        public void  run() {
            System.out.println("A1: before park");
            LockSupport.park();
            System.out.println("A1: after unpark ");
        }
    };


    private static Thread threadA2 = new Thread() {
        public void  run() {
            //睡一小會 保證A1先執行
            try {
                Thread.currentThread().sleep(50);
            }catch (InterruptedException e) {
                System.out.println("A2 interrupted");
            }
            System.out.println("A2: before unpark A1");
            try {
                Thread.currentThread().sleep(2000);
            }catch (InterruptedException e) {
                System.out.println("A2 interrupted");
            }
            System.out.println("A2: 2s later");
        }
    };


    /**
     *  A1 call park, then main call unpark(A1)
     *  期望:
     *     A1: before park
     *     A2: before unpark A1
     *     A2: 2s later
     *     A1: after park
     */
    protected static void testPark1() {
        threadA1.start();
        threadA2.start();
        try {
            Thread.currentThread().sleep(5000);
        }catch (InterruptedException e) {
            System.out.println("main interrupted");
        }
        LockSupport.unpark(threadA1);
    }

    private static Thread threadB1 = new Thread() {
        public void  run() {
            //睡一小會 保證main可以先執行unpark
            try {
                Thread.currentThread().sleep(50);
            }catch (InterruptedException e) {
                System.out.println("B1 interrupted");
            }
            System.out.println("B1: before park");
            LockSupport.park();
            //LockSupport.park();
            System.out.println("B1: after unpark ");
        }
    };


    private static Thread threadB2 = new Thread() {
        public void  run() {
            //比B1多睡50ms 保證B1先執行
            try {
                Thread.currentThread().sleep(100);
            }catch (InterruptedException e) {
                System.out.println("B2 interrupted");
            }
            System.out.println("B2: before unpark B1");
            try {
                Thread.currentThread().sleep(2000);
            }catch (InterruptedException e) {
                System.out.println("B2 interrupted");
            }
            System.out.println("B2: 2s later");
        }
    };
    /**
     *  main call unpark(B1), then B1 call park
     *  期望:
     *  B1: before park
     *  B1: after unpark
     *  B2: before unpark B1
     *  B2: 2s later
     */
    protected static void testPark2() {
        threadB1.start();
        //C睡50ms 保證unpark先調用
        LockSupport.unpark(threadB1);
        //LockSupport.unpark(threadC);
        threadB2.start();
    }


    private static Thread threadC1 = new Thread() {
        public void  run() {
            System.out.println("C1: before park");
            LockSupport.parkNanos(3000000000l);
            System.out.println("C1: after unpark 3s later");
        }
    };


    private static Thread threadC2 = new Thread() {
        public void  run() {
            //比C1多睡50ms 保證C1先執行
            try {
                Thread.currentThread().sleep(50);
            }catch (InterruptedException e) {
                System.out.println("C2 interrupted");
            }
            System.out.println("C2: before unpark C1");
            try {
                Thread.currentThread().sleep(2000);
            }catch (InterruptedException e) {
                System.out.println("C2 interrupted");
            }
            System.out.println("C2: 2s later");
        }
    };

    /**
     * C1 call parkNanos(3000000), then main call unpark(C1) 5s later
     *  期望:
     *  C1: before park
     *  C2: before unpark C1
     *  C2: 2s later
     *  C1: after unpark 3s later
     *  main: 5s later
     */
    protected static void testParkNanos() {
        threadC1.start();
        threadC2.start();
        try {
            Thread.currentThread().sleep(5000);
        }catch (InterruptedException e) {
            System.out.println("main interrupted");
        }
        System.out.println("main: 5s later");
        LockSupport.unpark(threadC1);
    }

    private static Thread threadD1 = new Thread() {
        public void  run() {
            System.out.println("D1: before park");
            //3s後
            Long deadLine = System.currentTimeMillis() + 3000;
            //過去的一個時間 則立即park不阻塞
            //Long deadLine = System.currentTimeMillis() - 1000;
            LockSupport.parkUntil(deadLine);
            System.out.println("D1: after unpark 3s later");
        }
    };


    private static Thread threadD2 = new Thread() {
        public void  run() {
            //比D1多睡50ms 保證D1先執行
            try {
                Thread.currentThread().sleep(50);
            }catch (InterruptedException e) {
                System.out.println("D2 interrupted");
            }
            System.out.println("D2: before unpark D1");
            try {
                Thread.currentThread().sleep(2000);
            }catch (InterruptedException e) {
                System.out.println("D2 interrupted");
            }
            System.out.println("D2: 2s later");
        }
    };

    /**
     * D1 call parkUtil(currentTime + 3000), then main call unpark(D1) 5s later
     *  期望:
     *  D1: before park
     *  D2: before unpark D1
     *  D2: 2s later
     *  D1: after unpark 3s later
     *  main: 5s later
     */
    protected static void testParkUtil() {
        threadD1.start();
        threadD2.start();
        try {
            Thread.currentThread().sleep(5000);
        }catch (InterruptedException e) {
            System.out.println("main interrupted");
        }
        System.out.println("main: 5s later");
        LockSupport.unpark(threadD1);
    }

    public static void main(String[] args) {
        System.out.println("**************");
        LockSupportDemo.testPark1();

        //睡6s保證 下面test不影響前面的test輸出
        try {
            Thread.currentThread().sleep(6000);
        }catch (InterruptedException e) {
            System.out.println("main interrupted");
        }

        System.out.println("**************");
        LockSupportDemo.testPark2();

        //睡6s保證 下面test不影響前面的test輸出
        try {
            Thread.currentThread().sleep(6000);
        }catch (InterruptedException e) {
            System.out.println("main interrupted");
        }

        System.out.println("**************");
        LockSupportDemo.testParkNanos();

        //睡6s保證 下面test不影響前面的test輸出
        try {
            Thread.currentThread().sleep(6000);
        }catch (InterruptedException e) {
            System.out.println("main interrupted");
        }

        System.out.println("**************");
        LockSupportDemo.testParkUtil();
    }
}

運行結果:

**************
A1: before park
A2: before unpark A1
A2: 2s later
A1: after unpark 
**************
B1: before park
B1: after unpark 
B2: before unpark B1
B2: 2s later
**************
C1: before park
C2: before unpark C1
C2: 2s later
C1: after unpark 3s later
main: 5s later
**************
D1: before park
D2: before unpark D1
D2: 2s later
D1: after unpark 3s later
main: 5s later

總結

總結一下LockSupport的使用要點:

  1. 線程調用park阻塞自己(類似sleep),直到中斷、超時或者其它線程調用unpark;
  2. 線程調用park之前,如果其它線程調用了unpark,則會立即返回;
  3. 線程喚醒以後,線程並不知道是什麼原因被喚醒,需要去判斷時間、線程狀態(Thread.interrupted());
  4. 線程start之前,unpark沒有用,線程start之後park仍然會阻塞;
  5. 線程調用park阻塞不會釋放線程佔用的資源(鎖);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章