【Java編程思想】線程的基本協作機制 與 線程的中斷

wait/notify

Java在Object類中定義了一些線程協作的基本方法,wait和notify

public final void wait() throws InterruptedException;
public final native void wait(long timeout) throws InterruptedException;

一個帶時間參數,單位是毫秒,表示最多等待的時長,如果爲0則表示無限期等待;
一個不帶時間參數,表示無限期等待,實際就是調用wait(0)
在等待期間都可以被中斷,如果被中斷,會拋出InterruptedException異常

每個對象都有一把鎖和等待隊列,一個線程在進入synchronized代碼塊時,會嘗試獲取鎖,如果獲取不到則會吧當前線程加入等待隊列中。除了用於鎖的等待隊列,每個對象還有另外一個等待隊列,表示條件隊列,該隊列用於線程間的協作。
調用wait就會把當前線程放到條件隊列上並阻塞,表示當前線程執行不下去了,它需要等待一個條件,需要其他線程來改變。當其他線程改變了條件後,應該調用Object的notify方法,notify做的就是從條件隊列中選一個線程,將其從隊列中移除並喚醒

// 從條件隊列中選一個線程,移除並喚醒
public final native void notify();
// 移除條件隊列中所有的線程並喚醒
public final native void notifyAll();

這裏做一個簡單的示例,一個線程在啓動後,在執行某個操作前,需要等待主線程給它的指令,收到指令後才執行

public class WaitThread extends Thread {

    private volatile boolean fire = false;

    @Override
    public void run() {
        synchronized (this) {
            try {
                while (!fire) {
                    System.out.println("start wait");
                    wait();
                    System.out.println("wait end");
                }
                System.out.println("fire !!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void fire() throws InterruptedException {
        this.fire = true;
        System.out.println("notify");
        notify();
        Thread.sleep(5000);
        System.out.println("notify !!!");
    }

    public static void main(String[] args) throws InterruptedException {
        WaitThread waitThread = new WaitThread();
        waitThread.start();
        Thread.sleep(1000);
        waitThread.fire();
    }
}

wait/notify 這方法只能在synchronized代碼塊內被調用
可能會有疑問,兩個方法都在synchronized代碼塊內,一個線程在wait時,另一個線程怎麼可以調用到notify呢?
實際上調用wait時,線程會釋放對象鎖

只有在把包含notify的synchronized代碼塊執行完以後,等待的線程纔會從wait調用中返回。

協作場景

  1. 生產者/消費者協作模式:生產者線程和消費者線程通過共享隊列進行協作,生產者將數據或任務放到隊列上,而消費者從隊列上取數據或任務。如果隊列長度有限,在隊列滿的時候,生產者需要等待,而在隊列爲空的時候,消費者需要等待。
    Java提供了專門的阻塞隊列實現:

    • 接口BlockingQueue和BlockingDeque
    • 基於數組的實現類ArrayBlockingQueue
    • 基於鏈表的實現類LinkedBlockingQueue和LinkedBlockingDeque
    • 基於堆的實現類PriorityBlockingQueue
  2. 同時開始: 要求多個線程能同時開始工作

  3. 等待結束:主線程將任務分解爲若干子任務,爲每個子任務創建一個線程,主線程在繼續執行其他任務之前需要等待每個子任務執行完畢
  4. 異步結果
  5. 集合點: 比如在並行迭代計算中,每個線程負責一部分計算,然後在集合點等待其他線程完成,所有線程到齊後,交換數據和結果,再進行下一次迭代。

線程的中斷

在Java中,停止一個線程的主要機制是中斷,中斷並不是強迫終止一個線程,它是一種協作機制,是給線程傳遞一個取消信號,但是由線程決定如何以及何時退出

// 返回對應線程的中斷標誌位是否爲true
public boolean isInterrupted(); 
// 中斷對應的線程
public void interrupte();
// 返回當前線程的中斷標誌位是否爲true,同時清空中斷標誌位  
public static boolean interrupted();

interrupted()對線程的影響與線程的狀態和在進行的IO操作有關。我們主要考慮線程的狀態,IO操作的影響和具體IO以及操作系統有關

RUNNABLE

如線程再運行或具備運行條件只是在等待操作系統調度,且沒有執行IO操作,interrupted()只是會設置線程的中斷標誌位,沒有任何其他作用。
不過我們可以在合適的位置檢查中斷標誌位

WAITING/TIMED_WAITING

線程調用join/wait/sleep方法會進入WAITING/TIMED_WAITING狀態,此時調用interrupt()會使得線程拋出InterruptedException。拋出異常後,中斷標誌位會被清空

BLOCKED

如果線程在等待鎖,對線程對象調用interrupted()只是會設置線程的中斷標誌位,線程依然會處於BLOCKED狀態

NEW/TERMINATED

如果線程尚未啓動(NEW),或者已經結束(TERMINATED),調用interrupted()對它沒有任何效果。

發佈了98 篇原創文章 · 獲贊 97 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章