線程運作的基本原理
在
java
應用程式中,使用
new Thread().start()
來啟動一個線程時,底層會進行怎樣的處理?我們通過一個簡單的流程圖來進一步分析:
如上圖,代碼中建立并啟動了一個線程,在
java
中調用相關方法通知到作業系統,作業系統首先接收到
JVM
指令會先建立一個線程出來,這時候線程并不會馬上執行,它會通過作業系統
JVM
排程算法把該線程配置設定給某個
CPU
來執行,
CPU
執行任務的時候就會回調線程中的
CPU
方法來執行相關指令。
run()
線程的運作狀态
一個線程從啟動到銷毀的這一個生命周期中會經曆各種不同的狀态,微觀上
java
應用中線程一共分為6種狀态:
- NEW:建立狀态,當執行
的時候線程處于此狀态。new Thread()
- RUNNABLE:運作狀态,線程調用
方法啟動線程後的狀态,一般線程調用start()
後會進入一個隊列就緒,等獲得CPU執行權後才真正開始執行線程中的start()
代碼塊。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());
}
- 直接捕獲異常輸出日志不做任何處理,線程繼續運作。
- 将異常抛出讓調用方處理。
- 列印異常資訊并停止目前線程。
- 記錄日志,結合資料庫或其他中間件做任務重試處理。