天天看點

Java多線程程式設計-LockSupport工具類使用及源碼分析LockSupport類

Java多線程程式設計-LockSupport工具類使用及源碼分析

  • LockSupport類
    • LockSupport.park()
      • 1.調用unpack方法獲得許可
      • 2.調用中斷interrupts方法獲得許可
    • parkNanos(long nanos)
    • park(Object blocker)
    • parkNanos(Object blocker, long nanos)
    • parkUntil(Object blocker, long deadline)

在Java工具包中有一個LockSupport工具類,主要負責挂起和喚醒線程。

LockSupport類

用于建立鎖和其他同步類的基本線程阻塞原語,此類與使用它的每個線程關聯一個許可。如果獲得許可,将立即傳回對park的調用,并在此過程中消耗掉它;否則may會被阻止。調用unpark可使許可證可用(如果尚不可用)。(不過與信号量不同,許可證不會累積。最多隻能有一個。)

方法park和unpark提供了有效的阻塞和解阻塞線程的方法,這些線程不會遇到導緻已棄用的方法Thread.suspend和Thread.resume無法用于以下問題:由于許可,在調用park的一個線程與試圖進行unpark的另一個線程之間的競争将保留生命力。此外,如果調用者的線程被中斷并且支援逾時版本,則park将傳回。 park方法也可能在其他任何時間出于“無理由”傳回,是以通常必須在循環中調用該循環,該循環在傳回時會重新檢查條件。從這個意義上說,park是對“繁忙等待”的優化,它不會浪費太多的時間,而必須與unpark配對才能生效。

了解了其作用,我們看看相關API。

LockSupport.park()

park():除非有許可,否則出于線程排程目的禁用目前線程。

如果許可證可用,則将其消耗掉,并立即傳回呼叫;否則,将立即傳回該呼叫。出于線程排程的目的,目前線程将被禁用,并處于休眠狀态,直到發生以下三種情況之一:

1.其他一些線程以目前線程為目标調用unpark()方法

2.其他一些線程Threadinterrupt interrupts目前線程

3.虛假地調用傳回

源碼:

public static void park() {
    UNSAFE.park(false, 0L);
}
           

LockSupport都是調用的UNSAFE的方法,我們先看看park方法

public class LockSupportMain {
    public static void main(String[] args) {
        System.out.println("begin park!");
        LockSupport.park();
        System.out.println("end park!");
    }
}
           

運作結果:

Java多線程程式設計-LockSupport工具類使用及源碼分析LockSupport類

我們調用了park方法,導緻Main線程一直被阻塞,一直沒有結束,因為預設的情況下,調用線程是不持有許可證的,我們如何解決呢?上面提到過三個方式,我們一個一個的驗證。

1.調用unpack方法獲得許可

unpack():如果給定線程尚不可用,則使它可用。如果線程在park上被阻止,則它将取消阻止。否則,将確定其對park的下一次調用不會阻塞。如果給定線程尚未啟動,則不能保證此操作完全無效。

源碼:

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}
           
public class LockSupportMain2 {
    public static void main(String[] args) {
        System.out.println("begin park!");
        LockSupport.unpark(Thread.currentThread());
        LockSupport.park();
        System.out.println("end park!");
    }
}
           

運作結果:

Java多線程程式設計-LockSupport工具類使用及源碼分析LockSupport類

可以看出,目前的線程main已經釋放了,沒有進行阻塞,直接運作完成了。

我們建立一個線程有Main線程進行unpark方法将線程在阻塞的情況下,進行運作。

public class LockSupportMain3 {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("begin start thread name: " + Thread.currentThread().getName() + " park");
                LockSupport.park();
                System.out.println("end start thread name: " + Thread.currentThread().getName() + " park");
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(2000);
        System.out.println("main thread call unpark");
        LockSupport.unpark(thread);
    }

}
           

運作結果:

Java多線程程式設計-LockSupport工具類使用及源碼分析LockSupport類

運作結果可以看出,其他一些線程以目前線程為目标調用unpark()方法可以将線程的park導緻阻塞的繼續運作。

2.調用中斷interrupts方法獲得許可

由于park方法不會告訴你何種原因傳回,是以調用者需要根據之前調用park方法的原因,再次檢查條件是否滿足,如果不能滿足,就還需要調用park方法

public class LockSupportMain4 {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("begin start thread name: " + Thread.currentThread().getName() + " park");
                while (!Thread.currentThread().isInterrupted()) {
                    LockSupport.park();
                }
                System.out.println("end start thread name: " + Thread.currentThread().getName() + " park");
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(2000);
        //通過interrupt方法 讓park阻塞繼續運作
        thread.interrupt();
    }

}
           

運作結果:

Java多線程程式設計-LockSupport工具類使用及源碼分析LockSupport類

由運作可以看出,其他一些線程Threadinterrupt interrupts目前線程是可以将park阻塞的線程繼續運作。

parkNanos(long nanos)

parkNanos(long nanos):除非允許使用許可,否則在指定的等待時間内禁用用于線程排程的目前線程。

源碼:

public static void parkNanos(long nanos) {
    if (nanos > 0)
        UNSAFE.park(false, nanos);
}
           
public class LockSupportMain5 {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("begin start thread name: " + Thread.currentThread().getName() + " park");
                LockSupport.parkNanos(3_000_000_000L);
                System.out.println("end start thread name: " + Thread.currentThread().getName() + " park");
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
           

運作結果:

Java多線程程式設計-LockSupport工具類使用及源碼分析LockSupport類

三秒之後,阻塞三秒之後繼續運作。

park(Object blocker)

park(Object blocker):除非有許可,否則出于線程排程目的禁用目前線程

源碼:

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

這裡的blocker對象是Thread類中的blocker對象,代碼如下:

//提供給java.util.concurrent.locks.LockSupport.park的目前調用的參數。
//由(私有)java.util.concurrent.locks.LockSupport.setBlocker設定使用
//java.util.concurrent.locks.LockSupport.getBlocker進行通路
volatile Object parkBlocker;
           

parkNanos(Object blocker, long nanos)

源碼:

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(Object blocker, long deadline)

parkUntil(Object blocker, long deadline):除非指定許可,否則禁用目前線程以進行線程排程,直到指定的期限。

源碼:

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

我們使用java API中的例子看看:

public class LockSupportMain6 {
    private final AtomicBoolean locked = new AtomicBoolean(false);
    private final Queue<Thread> waiters
            = new ConcurrentLinkedQueue<Thread>();

    public void lock() {
        boolean wasInterrupted = false;
        Thread current = Thread.currentThread();
        waiters.add(current);

        // 不在隊列中時先阻塞或無法擷取鎖定
        while (waiters.peek() != current ||
                !locked.compareAndSet(false, true)) {
            LockSupport.park(this);
            // 等待時忽略中斷 如果park方法是因為被中斷而傳回,則忽略中斷,并且重新設定中斷标記,做個标記
            if (Thread.interrupted()) {
                wasInterrupted = true;
            }
        }
        waiters.remove();
        // 退出時重新聲明中斷狀态
        if (wasInterrupted)          
        {
            current.interrupt();
        }
    }
    public void unlock() {
        locked.set(false);
        LockSupport.unpark(waiters.peek());
    }
}
           

有blocker的可以傳遞給開發人員更多的現場資訊,可以檢視到目前線程的阻塞對象,友善定位問題

繼續閱讀