溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

線程中斷 interrupt 和 LockSupport

發(fā)布時間:2020-08-11 13:55:19 來源:ITPUB博客 閱讀:186 作者:慕容扶蘇 欄目:編程語言
本文章將要介紹的內(nèi)容有以下幾點,讀者朋友也可先自行思考一下相關(guān)問題:
  1. 線程中斷 interrupt 方法怎么理解,意思就是線程中斷了嗎?那當前線程還能繼續(xù)執(zhí)行嗎?
  2. 判斷線程是否中斷的方法有幾個,它們之間有什么區(qū)別?
  3. LockSupport的 park/unpark 和 wait/notify 有什么區(qū)別?
  4. sleep 方法是怎么響應(yīng)中斷的?
  5. park 方法又是怎么響應(yīng)中斷的?

線程中斷相關(guān)方法

線程中和中斷相關(guān)的方法有三個,分別介紹如下:

1) interrupt

我們一般都說這個方法是用來中斷線程的,那么這個中斷應(yīng)該怎么理解呢? 就是說把當前正在執(zhí)行的線程中斷掉,不讓它繼續(xù)往下執(zhí)行嗎?

其實,不然。 此處,說的中斷僅僅是給線程設(shè)置一個中斷的標識(設(shè)置為true),線程還是會繼續(xù)往下執(zhí)行的。而線程怎么停止,則需要由我們自己去處理。 一會兒會用代碼來說明這個。

2) isInterrupted

判斷當前線程的中斷狀態(tài),即判斷線程的中斷標識是true還是false。 注意,這個方法不會對線程原本的中斷狀態(tài)產(chǎn)生任何影響。

3) interrupted

也是判斷線程的中斷狀態(tài)的。但是,需要注意的是,這個方法和 isInterrupted 有很大的不同。我們看下它們的源碼:

public boolean isInterrupted() {  
    return isInterrupted(false);  
}public static boolean interrupted() {  
    return currentThread().isInterrupted(true);  
}//調(diào)用同一個方法,只是傳參不同private native boolean isInterrupted(boolean ClearInterrupted);

首先 isInterrupted 方法是線程對象的方法,而 interrupted 是Thread類的靜態(tài)方法。

線程中斷 interrupt 和 LockSupport

其次,它們都調(diào)用了同一個本地方法 isInterrupted,不同的只是傳參的值,這個參數(shù)代表的是,是否要把線程的中斷狀態(tài)清除(清除即不論之前的中斷狀態(tài)是什么值,最終都會設(shè)置為false)。

因此,interrupted 靜態(tài)方法會把原本線程的中斷狀態(tài)清除,而 isInterrupted 則不會。所以,如果你調(diào)用兩次 interrupted 方法,第二次就一定會返回false,除非中間又被中斷了一次。

下面證明一下 interrupt 方法只是設(shè)置一個中斷狀態(tài),而不是使當前線程中斷運行:

public class TestFlag {    static volatile boolean flag = true;    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable(){            @Override
            public void run() {
                System.out.println("線程中斷標志:"+Thread.currentThread().isInterrupted());                while (flag){
                }
                System.out.println("標志flag為:" + flag);
                System.out.println("線程中斷標志:"+Thread.currentThread().isInterrupted());
                System.out.println("我還在繼續(xù)執(zhí)行");
            }
        });
        t.start();
        Thread.sleep(100);
        flag = false;
        t.interrupt();
    }
}

運行結(jié)果:

線程中斷標志:false標志flag為:false線程中斷標志:true我還在繼續(xù)執(zhí)行

當線程啟動,還沒調(diào)用中斷方法時,中斷狀態(tài)為false,然后調(diào)用中斷方法,并把flag設(shè)置為false。此時,run方法跳出while死循環(huán)。我們會發(fā)現(xiàn)線程的中斷狀態(tài)為true,但是線程還是會繼續(xù)往下執(zhí)行,直到執(zhí)行結(jié)束。

sleep 響應(yīng)中斷

線程中常用的阻塞方法,如sleep,join和wait 都會響應(yīng)中斷,然后拋出一個中斷異常 InterruptedException。但是,注意此時,線程的中斷狀態(tài)會被清除。所以,當我們捕獲到中斷異常之后,應(yīng)該保留中斷信息,以便讓上層代碼知道當前線程中斷了。通常有兩種方法可以做到。

一種是,捕獲異常之后,再重新拋出異常,讓上層代碼知道。另一種是,在捕獲異常時,通過 interrupt 方法把中斷狀態(tài)重新設(shè)置為true。

下面,就以sleep方法為例,捕獲中斷異常,然后重新設(shè)置中斷狀態(tài):

public class TestInterrupt {    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {            private int count = 0;            @Override
            public void run() {                try {
                    count = new Random().nextInt(1000);
                    count = count * count;
                    System.out.println("count:"+count);
                    Thread.sleep(5000);
                } catch (Exception e) {
                    System.out.println(Thread.currentThread().getName()+"線程第一次中斷標志:"+Thread.currentThread().isInterrupted());                    //重新把線程中斷狀態(tài)設(shè)置為true,以便上層代碼判斷
                    Thread.currentThread().interrupt();
                    System.out.println(Thread.currentThread().getName()+"線程第二次中斷標志:"+Thread.currentThread().isInterrupted());
                }
            }
        });
        t.start();
        Thread.sleep(100);
        t.interrupt();
    }
}

結(jié)果:

count:208849
Thread-0線程第一次中斷標志:falseThread-0線程第二次中斷標志:true

LockSupport方法介紹

LockSupport 方法中重要的兩個方法就是park 和 unpark 。

park和interrupt中斷

park方法可以阻塞當前線程,如果調(diào)用unpark方法或者中斷當前線程,則會從park方法中返回。

park方法對中斷方法的響應(yīng)和 sleep 有一些不太一樣。它不會拋出中斷異常,而是從park方法直接返回,不影響線程的繼續(xù)執(zhí)行。我們看下代碼:

public class LockSupportTest {    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new ParkThread());
        t.start();
        Thread.sleep(100); //①
        System.out.println(Thread.currentThread().getName()+"開始喚醒阻塞線程");
        t.interrupt();
        System.out.println(Thread.currentThread().getName()+"結(jié)束喚醒");
    }
}class ParkThread implements Runnable{    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"開始阻塞");
        LockSupport.park();
        System.out.println(Thread.currentThread().getName()+"第一次結(jié)束阻塞");
        LockSupport.park();
        System.out.println("第二次結(jié)束阻塞");
    }
}

打印結(jié)果如下:

Thread-0開始阻塞
main開始喚醒阻塞線程
main結(jié)束喚醒
Thread-0第一次結(jié)束阻塞
第二次結(jié)束阻塞

當調(diào)用interrupt方法時,會把中斷狀態(tài)設(shè)置為true,然后park方法會去判斷中斷狀態(tài),如果為true,就直接返回,然后往下繼續(xù)執(zhí)行,并不會拋出異常。注意,這里并不會清除中斷標志。

unpark

unpark會喚醒被park的指定線程。但是,這里要說明的是,unpark 并不是簡單的直接去喚醒被park的線程??聪翵DK的解釋:

線程中斷 interrupt 和 LockSupport

unpark只是給當前線程設(shè)置一個許可證。如果當前線程已經(jīng)被阻塞了(即調(diào)用了park),則會轉(zhuǎn)為不阻塞的狀態(tài)。如若不然,下次調(diào)用park方法的時候也會保證不阻塞。這句話的意思,其實是指,park和unpark的調(diào)用順序無所謂,只要unpark設(shè)置了這個許可證,park方法就可以在任意時刻消費許可證,從而不會阻塞方法。

還需要注意的是,許可證最多只有一個,也就是說,就算unpark方法調(diào)用多次,也不會增加許可證。 我們可以通過代碼驗證,只需要把上邊代碼修改一行即可:

//LockSupportTest類//原代碼t.interrupt();//修改為LockSupport.unpark(t);
LockSupport.unpark(t);

就會發(fā)現(xiàn),只有第一次阻塞會被喚醒,但是第二次依然會繼續(xù)阻塞。結(jié)果如下:

Thread-0開始阻塞
main開始喚醒阻塞線程
main結(jié)束喚醒
Thread-0第一次結(jié)束阻塞

另外,在此基礎(chǔ)上,把主線程的sleep方法去掉(代碼中①處),讓主線程先運行,也就是有可能先調(diào)用unpark方法,然后子線程才開始調(diào)用park方法阻塞。我們會發(fā)現(xiàn),出現(xiàn)以下結(jié)果,證明了上邊我說的park方法和unpark不分先后順序,park方法可以隨時消費許可證。

main開始喚醒阻塞線程
main結(jié)束喚醒
Thread-0開始阻塞
Thread-0第一次結(jié)束阻塞

park/unpark和 wait/notify區(qū)別

了解了 park/unpark的用法之后,想必你也能分析出來它們和 wait、notify有什么不同之處了。

  1. wait和notify方法必須和同步鎖 synchronized一塊兒使用。而park/unpark使用就比較靈活了,沒有這個限制,可以在任何地方使用。

  2. park/unpark 使用時沒有先后順序,都可以使線程不阻塞(前面代碼已驗證)。而wait必須在notify前先使用,如果先notify,再wait,則線程會一直等待。

  3. notify只能隨機釋放一個線程,并不能指定某個特定線程,notifyAll是釋放鎖對象中的所有線程。而unpark方法可以喚醒指定的線程。

  4. 調(diào)用wait方法會使當前線程釋放鎖資源,但使用的前提是必須已經(jīng)獲得了鎖。 而park不會釋放鎖資源。(以下代碼驗證)

public class LockSyncTest {    private static Object lock = new Object();    //保存調(diào)用park的線程,以便后續(xù)喚醒
    private static Thread parkedThread;    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{             synchronized (lock){
                 System.out.println("unpark前");
                 LockSupport.unpark(parkedThread);
                 System.out.println("unpark后");
             }
        });
        Thread t2 = new Thread(new Runnable() {            @Override
            public void run() {                //和t1線程用同一把鎖時,park不會釋放鎖資源,若換成this鎖,則會釋放鎖
                synchronized (lock){
                    System.out.println("park前");
                    parkedThread = Thread.currentThread();
                    LockSupport.park();                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("park后");
                }
            }
        });
        t2.start();
        Thread.sleep(100);
        t1.start();
    }
}//打印結(jié)果//park前

以上代碼,會一直卡在t2線程,因為park不會釋放鎖,因此t1也無法執(zhí)行。

如果把t2的鎖換成this鎖,即只要和t1不是同一把鎖,則t1就會正常執(zhí)行,然后把t2線程喚醒。打印結(jié)果如下:

park前
unpark前
unpark后
park后
向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI