1. 程式人生 > 其它 >LockSupport、interrupt執行緒中斷

LockSupport、interrupt執行緒中斷

LockSupport是一個執行緒阻塞工具類,所有的方法都是靜態方法,可以讓執行緒在任意位置阻塞,當然阻塞之後肯定得有喚醒的方法。

public static void park(Object blocker); // 暫停當前執行緒
public static void parkNanos(Object blocker, long nanos); // 暫停當前執行緒,不過有超時時間的限制
public static void parkUntil(Object blocker, long deadline); // 暫停當前執行緒,直到某個時間
public static void park(); // 無期限暫停當前執行緒
public static void parkNanos(long nanos); // 暫停當前執行緒,不過有超時時間的限制 public static void parkUntil(long deadline); // 暫停當前執行緒,直到某個時間 public static void unpark(Thread thread); // 恢復當前執行緒 public static Object getBlocker(Thread t);

jdk文件解釋:

- park

  為了執行緒排程,禁用當前執行緒,除非許可可用。

  如果許可可用,則使用該許可,並且該呼叫立即返回;否則,為執行緒排程禁用當前執行緒,並在發生以下三種情況之一以前,使其處於休眠狀態

  1、其他某個執行緒將當前執行緒作為目標呼叫unpark;

  2、其他某個執行緒中斷當前執行緒;

  3、該呼叫不符合邏輯;

此方法並不報告哪個執行緒導致該方法返回。呼叫者應該重新檢查最先導致執行緒暫停的條件。呼叫者還可以確定執行緒返回時的中斷狀態。

-unpark

public static void unpark(Thread thread){}

  如果給定執行緒的許可尚不可用,則使其可用。如果執行緒在park上受阻塞,則它將解除其阻塞狀態。否則,保證下一次呼叫park不會受到阻塞。如果給定執行緒尚未啟動,則無法保證此操作有任何效果。

所以,park之後當前執行緒進入阻塞狀態,unpark或者中斷可以喚醒。park和unpark沒有先後順序之分。unpark會讓執行緒獲得許可,但最多為1。

Thread.interrupt()為什麼可以有unpark()的效果呢? interrupt 實際上會呼叫 interrupt0, interrpt0是一個native方法,會呼叫thread.cpp,thread.cpp會呼叫 os_linux.cpp,裡面程式碼思路大致就是 獲取到作業系統中對應java的執行緒,然後設定標誌位,再設定記憶體屏障,再去unpark執行緒,為什麼會去unpark執行緒呢?因為如果是park狀態,你連cpu時間片都分不到,談何執行/通知?

  • LockSupport.park()會檢查執行緒是否設定了中斷標誌位,如果設定了,則返回(這裡並不會清除中斷標誌位)。執行緒被中斷後,park不會阻塞。
public class LockSupportDemo {

    public static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");

    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super(name);
        }
        @Override public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                LockSupport.park();
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("被中斷了");
                }
                System.out.println("繼續執行");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        t1.start();
        Thread.sleep(1000L);
        t2.start();
        Thread.sleep(3000L);
        t1.interrupt();
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}


結果:
in t1
被中斷了
繼續執行
in t2
繼續執行

  

中斷

  如果一個執行緒處於了阻塞狀態(如執行緒呼叫了thread.sleep、thread.join、thread.wait、1.5中的condition.await、以及可中斷的通道上的 I/O 操作方法後可進入阻塞狀態),則線上程在檢查中斷標示時如果發現中斷標示為true,則會在這些阻塞方法(sleep、join、wait、1.5中的condition.await及可中斷的通道上的 I/O 操作方法)呼叫處丟擲InterruptedException異常,並且在丟擲異常後立即將執行緒的中斷標示位清除,即重新設定為false。丟擲異常是為了執行緒從阻塞狀態醒過來,並在結束執行緒前讓程式設計師有足夠的時間來處理中斷請求。

  注,synchronized在獲鎖的過程中是不能被中斷的,意思是說如果產生了死鎖,則不可能被中斷(請參考後面的測試例子)。與synchronized功能相似的reentrantLock.lock()方法也是一樣,它也不可中斷的,即如果發生死鎖,那麼reentrantLock.lock()方法無法終止,如果呼叫時被阻塞,則它一直阻塞到它獲取到鎖為止。但是如果呼叫帶超時的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那麼如果執行緒在等待時被中斷,將丟擲一個InterruptedException異常,這是一個非常有用的特性,因為它允許程式打破死鎖。你也可以呼叫reentrantLock.lockInterruptibly()方法,它就相當於一個超時設為無限的tryLock方法。

一、沒有任何語言方面的需求一個被中斷的執行緒應該終止。中斷一個執行緒只是為了引起該執行緒的注意,被中斷執行緒可以決定如何應對中斷。

二、對於處於sleep,join等操作的執行緒,如果被呼叫interrupt()後,會丟擲InterruptedException,然後執行緒的中斷標誌位會由true重置為false,因為執行緒為了處理異常已經重新處於就緒狀態。

三、不可中斷的操作,包括進入synchronized段以及Lock.lock(),inputSteam.read()等,呼叫interrupt()對於這幾個問題無效,因為它們都不丟擲中斷異常。如果拿不到資源,它們會無限期阻塞下去。

對於Lock.lock(),可以改用Lock.lockInterruptibly(),可被中斷的加鎖操作,它可以丟擲中斷異常。等同於等待時間無限長的Lock.tryLock(long time,TimeUnit unit)。

對於inputStream等資源,有些(實現了interruptibleChannel介面)可以通過close()方法將資源關閉,對應的阻塞也會被放開。

 

首先,看看Thread類裡的幾個方法:

public static booleaninterrupted 測試當前執行緒是否已經中斷。執行緒的中斷狀態由該方法清除。換句話說,如果連續兩次呼叫該方法,則第二次呼叫將返回 false。

public booleanisInterrupted()

測試執行緒是否已經中斷。執行緒的中斷狀態不受該方法的影響。

public voidinterrupt()

中斷執行緒。

上面列出了與中斷有關的幾個方法及其行為,可以看到interrupt是中斷執行緒。如果不瞭解Java的中斷機制,這樣的一種解釋極容易造成誤解,認為呼叫了執行緒的interrupt方法就一定會中斷執行緒。

其實,Java的中斷是一種協作機制。也就是說呼叫執行緒物件的interrupt方法並不一定就中斷了正在執行的執行緒,它只是要求執行緒自己在合適的時機中斷自己。每個執行緒都有一個boolean的中斷狀態(這個狀態不在Thread的屬性上),interrupt方法僅僅只是將該狀態置為true。

比如對正常執行的執行緒呼叫interrupt()並不能終止他,只是改變了interrupt標示符。

一般說來,如果一個方法宣告丟擲InterruptedException,表示該方法是可中斷的,比如wait,sleep,join,也就是說可中斷方法會對interrupt呼叫做出響應(例如sleep響應interrupt的操作包括清除中斷狀態,丟擲InterruptedException),異常都是由可中斷方法自己丟擲來的,並不是直接由interrupt方法直接引起的。

Object.wait, Thread.sleep方法,會不斷的輪詢監聽 interrupted 標誌位,發現其設定為true後,會停止阻塞並丟擲 InterruptedException異常。