天天看點

聊一聊線程是如何運作的

線程運作的基本原理

在​

​java​

​應用程式中,使用​

​new Thread().start()​

​來啟動一個線程時,底層會進行怎樣的處理?我們通過一個簡單的流程圖來進一步分析:

聊一聊線程是如何運作的
如上圖,​

​java​

​代碼中建立并啟動了一個線程,在​

​JVM​

​中調用相關方法通知到作業系統,作業系統首先接收到​

​JVM​

​指令會先建立一個線程出來,這時候線程并不會馬上執行,它會通過作業系統​

​CPU​

​排程算法把該線程配置設定給某個​

​CPU​

​來執行,​

​CPU​

​執行任務的時候就會回調線程中的​

​run()​

​方法來執行相關指令。

線程的運作狀态

一個線程從啟動到銷毀的這一個生命周期中會經曆各種不同的狀态,微觀上​

​java​

​應用中線程一共分為6種狀态:

  • NEW:建立狀态,當執行​

    ​new Thread()​

    ​的時候線程處于此狀态。
  • RUNNABLE:運作狀态,線程調用​

    ​start()​

    ​方法啟動線程後的狀态,一般線程調用​

    ​start()​

    ​後會進入一個隊列就緒,等獲得CPU執行權後才真正開始執行線程中的​

    ​run()​

    ​代碼塊。
  • BLOCKED:阻塞狀态,當線程在執行​

    ​synchronized​

    ​代碼塊,沒有搶占到同步鎖時會變成阻塞狀态。
  • WAITING:等待狀态,當調用​

    ​Object.wait()​

    ​方法時,線程會進入該等待狀态。
  • TIMED_WAITING:逾時等待狀态,例如​

    ​Thread.sheep(timeout)​

    ​逾時後會自動喚醒線程。
  • TERMINATED:終止狀态,當線程中的​

    ​run()​

    ​方法正常執行完或者調用​

    ​interrupt()​

    ​的時候線程變為此狀态。

從宏觀上看就分為五種狀态:建立、就緒、運作、等待、死亡,整體的狀态運作流轉如下圖:

聊一聊線程是如何運作的

如何終止線程

首先​

​run()​

​方法中的指令正常運作結束後線程自然會進入終止狀态。那麼如果我們想要終止一個運作中的線程該怎麼辦?

使用stop()終止

使用​

​stop()​

​​方法,該方式肯定是行不通的,該方法會強制停止一個線程的執行,并且會釋放線程中所占用的鎖,這種鎖的釋放是不可控的。

static class StopThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 100000; i++) {
            System.out.println("count:" + i);
        }
        System.out.println("thread run finish!");
    }
}

public static void main(String[] args) throws InterruptedException {
    StopThread stopThread = new StopThread();
    stopThread.start();
    Thread.sleep(50);
    stopThread.stop();
}      

由以上代碼所展示的在​

​for​

​循環未結束時就提前終止線程,導緻最後的​

​System.out.println("thread run finish!");​

​不會正常執行結束。

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}      

進入​

​println​

​的源碼看一下,我們就能夠發現有​

​print(x)、newLint()​

​兩個操作是原子性的,是以增加了​

​synchronized​

​同步鎖進行保護,按正常是不應該出現問題的,但是執行​

​stop()​

​操作會強制釋放所有鎖,進而導緻​

​println()​

​操作的原子性被破壞(上面的代碼多運作幾次就可能出現最後一次循環沒有換行,就是存在​

​newLine()​

​​未被執行的可能),是以實際開發過程中是一定不能使用​

​stop()​

​​來中斷線程的。

聊一聊線程是如何運作的
聊一聊線程是如何運作的

使用interrupt()終止

​Thread​

​類也提供了一個方法​

​interrupt()​

​,從單詞意義上看就是中斷的意思,但實際操作上并不像​

​stop()​

​那樣直接了斷,而是通過一個信号量的方式來通知線程中斷的。那麼這種情況也是需要有線程自己來覺得是否終止,但是要想讓線程安全中斷就需要做兩件事:

  • 外部線程需要發送一個中斷信号給正在運作中的線程。
  • 正常運作中的線程需要根據該信号來判斷是否終止線程。

根據以上的條件,我們用一個簡單的例子,通過​

​interrupt()​

​方法進行信号傳遞,具體代碼如下:

static class InterruptThread extends Thread {
    @Override
    public void run() {
        int i = 0;
        while (!this.isInterrupted()) {
            i++;
        }
        System.out.println("thread interrupt in:" + i);
    }
}

public static void main(String[] args) throws InterruptedException {
    InterruptThread interruptThread = new InterruptThread();
    interruptThread.start();
    TimeUnit.MILLISECONDS.sleep(50);
    System.out.println("interrupt status is:" + interruptThread.isInterrupted());
    interruptThread.interrupt();
    System.out.println("interrupt status is:" + interruptThread.isInterrupted());
}      
聊一聊線程是如何運作的

上述代碼中,首先建立并開啟一個線程​

​InterruptThread​

​,該線程​

​run()​

​方法中使用​

​while​

​循環進行計數,判斷條件為​

​!this.isInterrupted()​

​目前線程是否為中斷狀态,如果線程調用​

​interrupt()​

​方法那麼​

​isInterrupted()=true​

​,​

​while​

​條件不通過就停止循環列印控制台日志,線程運作結束。

從這個示例可以看出線程在調用​

​interrupt()​

​方法後并沒有直接了斷的把線程中斷掉,而是通過傳遞消息的形式來決定是否停止線程,這樣就可以在收到中斷信号後繼續把​

​run()​

​方法後面的代碼指令執行完,最終達成線程安全中斷的目的。

如何中斷阻塞狀态的線程

如果一個線程處于阻塞狀态,那麼能否也通過​

​interrupt()​

​方法進行中斷?答案肯定是可以的,具體要怎麼操作我們還是先上代碼分析:

static class BlockedInterruptThread extends Thread {
    @Override
    public void run() {
        int i = 0;
        while (!this.isInterrupted()) {
            try {
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            i++;
        }
        System.out.println("thread interrupt in:" + i);
    }
}

public static void main(String[] args) throws InterruptedException {
    BlockedInterruptThread blockedInterruptThread = new BlockedInterruptThread();
    blockedInterruptThread.start();
    TimeUnit.MILLISECONDS.sleep(50);
    System.out.println("interrupt status is:" + blockedInterruptThread.isInterrupted());
    blockedInterruptThread.interrupt();
    System.out.println("interrupt status is:" + blockedInterruptThread.isInterrupted());
}      
聊一聊線程是如何運作的

上面這段代碼看是使用​

​interrupt()​

​通知線程進行中斷,但是運作結果我們會發現其實線程并沒有被中斷,而是列印出了異常堆棧資訊并且還在運作中。

為什麼我們發出​

​interrupt()​

​指令而為什麼線程沒有被中斷呢?根據上面我們描述的狀态流轉圖可以看到,線程的狀态是不可能從直接狀态直接終止的,而是處于阻塞狀态的線程必須也隻能先進入就緒狀态,再進入運作狀态之後才能正常終止。是以上面的代碼抛出的​

​InterruptedException​

​異常就是因為線程處于阻塞中被提前喚醒了,也就是說在休眠阻塞時間未結束提前喚醒線程進入了就緒狀态。

static class BlockedInterruptThread extends Thread {
    @Override
    public void run() {
        int i = 0;
        while (!this.isInterrupted()) {
            try {
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
                // 再次發起中斷
                this.interrupt();
            }
            i++;
        }
        System.out.println("thread interrupt in:" + i);
    }
}

public static void main(String[] args) throws InterruptedException {
    BlockedInterruptThread blockedInterruptThread = new BlockedInterruptThread();
    blockedInterruptThread.start();
    TimeUnit.MILLISECONDS.sleep(50);
    System.out.println("interrupt status is:" + blockedInterruptThread.isInterrupted());
    blockedInterruptThread.interrupt();
    System.out.println("interrupt status is:" + blockedInterruptThread.isInterrupted());
}      
  • 直接捕獲異常輸出日志不做任何處理,線程繼續運作。
  • 将異常抛出讓調用方處理。
  • 列印異常資訊并停止目前線程。
  • 記錄日志,結合資料庫或其他中間件做任務重試處理。

總結