[溫故]圖解java多線程設計模式(二)

1、join & interrupt

這倆方法屬於線程對象裏的方法,屬於線程自己的操做。html

1.1:join方法

用於等待一個線程的終止,等待期間將會阻塞,直到被等待的線程終止結束。java

因此join能夠用來作多任務異步處理,好比仍是拿利用CompletableFuture優化程序的執行效率這篇裏的第一個例子作優化,這篇文章裏使用線程池的future模式進行多任務異步處理,如今使用join改寫下:web

再來簡單貼下這幾個方法shell

private String getTop() { // 這裏假設getTop須要執行200ms
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "頂部banner位";
    }

    private String getLeft() { // 這裏假設getLeft須要執行50ms
        try {
            Thread.sleep(50L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "左邊欄";
    }

    private String getRight() { // 這裏假設getRight須要執行80ms
        try {
            Thread.sleep(80L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "右邊欄";
    }

    private String getUser() { // 這裏假設getUser須要執行100ms
        try {
            Thread.sleep(100L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "用戶信息";
    }

而後如今使用簡單的線程作異步處理:異步

// 簡單異步獲取
    public WebModule getWebModuleMsgSimpleAsync() throws ExecutionException, InterruptedException {

        WebModule webModule = new WebModule();

        Thread topTask = new Thread(() -> webModule.setTop(this.getTop()));
        Thread leftTask = new Thread(() -> webModule.setLeft(this.getLeft()));
        Thread rightTask = new Thread(() -> webModule.setRight(this.getRight()));
        Thread userTask = new Thread(() -> webModule.setUser(this.getUser()));

        //觸發各個異步任務
        topTask.start();
        leftTask.start();
        rightTask.start();
        userTask.start();

        //等待全部的任務均執行完畢
        topTask.join();
        leftTask.join();
        rightTask.join();
        userTask.join();

        return webModule;
    }

測試代碼:oop

@Test
    public void testSimpleASync() throws Exception {
        // 同步方法測試,預估耗時200ms
        long start = System.currentTimeMillis();
        WebModule module = webHome.getWebModuleMsgSimpleAsync();
        System.out.println("經過異步方法獲取首頁所有信息消耗時間:" + (System.currentTimeMillis() - start) + "ms");
        System.out.println("結果爲:" + module.toString());
    }

測試結果:測試

經過異步方法獲取首頁所有信息消耗時間:272ms
結果爲:top: 頂部banner位;  left: 左邊欄;  right: 右邊欄;  user: 用戶信息

比預估的要多72ms,通過後來的測試,發現這72ms耗時發生在線程建立的時候,以及後續線程狀態轉換帶來的消耗,下面等待異步結束的時間約等於200ms,符合預期。優化

1.2:interrupt方法

用於主動終止一個線程,線程自己調用該方法後,視爲已終止狀態,join解除阻塞,下面來用interrupt和join來作個實驗:this

public class JoinTest {

    private boolean isStop = false;

    public static void main(String[] args) throws Exception {
        JoinTest test = new JoinTest();
        Thread loopT = new Thread(test::loopTask);
        loopT.start();

        sleep(2000L); //2s後終止線程
        test.setStop(true);

        long s = System.currentTimeMillis();
        loopT.join();
        System.out.println("線程終止後,join阻塞時間爲:" + (System.currentTimeMillis() - s));
        System.out.println("end~");
    }

    public void setStop(boolean stop) {
        isStop = stop;
    }

    public void loopTask() {
        while (!isStop) { //若狀態爲false,則繼續執行下面的邏輯,每隔1s打印一次
            sleep(1000L);
            System.out.println("loop trigger ~");
        }
        Thread.currentThread().interrupt(); //在這裏終止掉當前線程
        //事實上,在終止掉線程後,還有接下來的邏輯要執行
        long s = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            int[] a = new int[100]; //模擬耗時操做,這裏不能用sleep了,由於當前線程已經被終止了
        }
        System.out.println("線程終止後,邏輯塊運行時間:" + (System.currentTimeMillis() - s));
    }

    public static void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

執行結果:spa

loop trigger ~
loop trigger ~
線程終止後,邏輯塊運行時間:129
線程終止後,join阻塞時間爲:129
end~

即使線程被終止了,後面的邏輯也會觸發,join依舊會選擇阻塞,直到後續邏輯執行完畢,事實上,大部分任務均可以及時的終止,好比第一個例子,異步出去的任務,最終都會執行完成,線程變爲終止狀態,join均可以順利結束,可是反觀上例,若是沒人及時的設置isStop的值,程序會一直執行下去,沒有終止態,join會無止境的終止下去,這裏提一下stop,線程的stop方法已被官方標記爲「不建議使用」的方法,若是把上例的interrupt的調用換成stop,來看看其運行結果:

loop trigger ~
loop trigger ~
線程終止後,join阻塞時間爲:0
end~

能夠看到,線程終止後的後續邏輯均沒有觸發,等於說stop是一種很粗暴的終止線程的方式,一旦被stop,那麼裏面的業務邏輯將直接斷掉,所以官方並不推薦使用該方法來終止線程。

而interrupt,僅僅是對目標線程發送了了一箇中斷信號(改變了線程的中斷狀態而已),當目標線程再次經過obj.wait、thread.sleep、thread.join方法進入阻塞狀態時,接收到該信號,就會拋出InterruptedException異常,這時候須要業務方自行處理或者直接拋出,以結束線程阻塞狀態(這裏須要注意的是被obj.wait方法阻塞時,拋出該異常須要目標線程再次得到實例對象obj的鎖才行)。

上述三個「須要花費時間」的方法均拋出了InterruptedException異常,針對這些特性,想要完成如下操做就很是方便了:

①取消wait方法等待notify/notifyAll的處理

②取消在sleep方法指定時間內中止的處理

③取消join方法等待其餘線程終止的處理

取消以後所作的處理,取決於需求,可能會終止線程,或者通知用戶已取消,或者終止當前處理進入下一個處理階段。

2、線程狀態遷移圖

圖1

上面的圖太多太雜,咱們經過對一些能夠影響線程狀態的操做的分類,來簡化一下上面的圖:

 

圖2

相關文章
相關標籤/搜索