本篇講講如何終止子線程
暴力停止——Stop方法
package com.qcy.testStopThread;
/**
* @author qcy
* @create 2020/09/16 09:40:34
*/
public class Main1 {
static class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
System.out.println(i);
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//保證子線程進入運作狀态,避免還沒運作就被終止
Thread.sleep(100);
//暴力停止子線程
myThread.stop();
}
}
輸出如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iM2MjM4QjM3EGNxMjM2ITNzYzXzETMxcTMxIzLcBTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
可以看得出,線程确實被終止了。
但是,這個是一個被廢棄掉的方法,Stop方法上的注釋如下:
從上面的注釋,我們可以得到以下的資訊:
- Stop方法時被廢棄掉的方法,不推薦使用
- 使用Stop方法,會一直向上傳播ThreadDeath異常,進而使得目标線程解鎖所有鎖住的螢幕,即釋放掉所有的對象鎖。使得之前被鎖住的對象得不到同步的處理,是以可能會造成資料不一緻的問題。
- 注釋中建議我們取代Stop方法,可以增加一個變量。目标線程周期性地檢查這個變量。如果變量在某個時間訓示線程終止,則目标線程将以有序的方式從run方法中傳回。
- 當然,如果目标線程長時間進行等待,則可以使用中斷的方式來終止線程。
是以,我們打算在下面的方法中,使用變量或中斷的方式來終止線程。
周期性檢查條件變量
通過指定一個條件變量,外部線程(比如是主線程)可以控制該變量,内部線程(比如子線程)在内部循環檢查該變量。為了保證條件變量在記憶體中的可見性,我們使用volatile修飾它。有關volatile是如何保證可見性的,可以參考我的另外一篇文章多線程之記憶體可見性
package com.qcy.testStopThread;
/**
* @author qcy
* @create 2020/09/16 10:30:30
*/
public class Main2 {
static class MyThread extends Thread {
//條件變量
private volatile boolean stop = false;
private int i = 0;
public void finish() {
stop = true;
}
@Override
public void run() {
//循環檢查條件變量
while (!stop) {
//業務代碼
i++;
System.out.println(i);
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//保證子線程進入運作狀态,避免還沒運作就被終止
Thread.sleep(100);
//修改條件變量為false
myThread.finish();
}
}
可以看得出,如果我們的業務要求在每一次循環結束後去檢查條件變量,那麼以上的寫法是沒有問題的。
但是,以上方式的即時性不高,如果我們的業務代碼有等待操作,比如sleep與wait操作,那麼如何在這些操作中就終止線程呢?
條件變量結合中斷
package com.qcy.testStopThread;
/**
* @author qcy
* @create 2020/09/16 11:51:30
*/
public class Main3 {
static class MyThread extends Thread {
//條件變量
private volatile boolean stop = false;
private int i = 0;
public void finish() {
stop = true;
this.interrupt();
}
@Override
public void run() {
//循環檢查條件變量
while (!stop) {
//業務代碼
i++;
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("線程在sleep期間被打斷了");
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//保證子線程進入運作狀态,避免還沒運作就被終止
Thread.sleep(1000);
//停止子線程
myThread.finish();
}
}
輸出:
interrupt方法用來設定線程的中斷狀态,如果目标線程正阻塞于wait、sleep等方法時,首先會清除目前線程的中斷狀态,然後抛出java.lang.InterruptedException異常。
可以看得出,子線程确實被終止掉了。
能不能使用Thread.isInterrupted()來代替條件變量呢?
使用Thread.isInterrupted()來代替條件變量
我們将代碼稍微改造一下,使用Thread.isInterrupted()用來擷取目标線程是否處于中斷狀态。
package com.qcy.testStopThread;
/**
* @author qcy
* @create 2020/09/16 14:03:00
*/
public class Main4 {
static class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
//業務代碼
i++;
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("線程在sleep期間被打斷了");
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//保證子線程進入運作狀态,避免還沒運作就被終止
Thread.sleep(1000);
//停止子線程
myThread.interrupt();
}
}
觀察輸出:
什麼情況,打斷了為什麼還能繼續輸出?
在上面其實已經說過了,interrupt方法用來設定線程的中斷狀态,如果目标線程正阻塞于wait、sleep等方法時,首先會清除目前線程的中斷狀态,然後抛出java.lang.InterruptedException異常。
目标線程正在進行第11次循環,進入了sleep操作中,此時收到了主線程的interrupt操作,目标線程擁有了中斷狀态,則先清除中斷狀态,然後抛出異常,下一次循環檢測Thread.currentThread().isInterrupted()時,isInterrupted依然傳回false,進而繼續進行循環操作。
那怎麼停止目前線程呢,很簡單,處理異常時,再次打斷即可,代碼如下:
package com.qcy.testStopThread;
/**
* @author qcy
* @create 2020/09/16 14:03:00
*/
public class Main4 {
static class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
//業務代碼
i++;
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("線程在sleep期間被打斷了");
e.printStackTrace();
//再次打斷,設定中斷标志,則之後的isInterrupted為true
this.interrupt();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//保證子線程進入運作狀态,避免還沒運作就被終止
Thread.sleep(1000);
//停止子線程
myThread.interrupt();
}
}
輸出如下: