停止線程的方式
在 Java 中停止線程的方式有三種:
1、線程的 run 方法執(zhí)行到最后,線程正常結(jié)束并退出;
2、使用 Thread 類提供的 stop 方法去停止一個正在運行的線程,該方法由于存在安全問題已被標記為過時,不推薦使用了;
3、使用 Java 中推薦的方式,即使用線程中斷機制,去中斷線程;
三種方式介紹
1、run 方法自行結(jié)束,這個是線程正常執(zhí)行完后結(jié)束,沒什么值得說的;
2、Thread 類的 stop 方法;
這個是一個在 Java 中被標記為過時的方法,因為該方法有線程安全方面的風(fēng)險,因此已經(jīng)不推薦使用了;具體原因可以參考源碼中的解釋,已經(jīng)寫的非常清楚了,下面舉個例子說明:
public static void main(String[] args) throws InterruptedException {
TestObject object = new TestObject(1, 1);
TestRunnable testRunnable = new TestRunnable(object);
Thread t1 = new Thread(testRunnable);
Thread t2 = new Thread(testRunnable);
t1.start();
t2.start();
// t1.stop();
t1.join();
t2.join();
System.out.println(object.getX() == object.getY());
System.out.println(object.getX() );
System.out.println(object.getY());
System.out.println("main end");
}
private static class TestObject{
private int x ;
private int y ;
TestObject(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
private static class TestRunnable implements Runnable {
private final Object lock = new Object();
private final TestObject object;
TestRunnable(TestObject object) {
this.object = object;
}
@Override
public void run() {
synchronized (lock){
object.setX(object.getX() + 1);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
object.setY(object.getY() + 1);
}
}
}
結(jié)果
false
3
2
如上 demo,基本上可以說明 stop 的線程安全問題了。在上面的示例中,runnable 任務(wù)分別將對象 TestObject 中的屬性 x 和屬性 y 加1,中間休息了 100 毫秒,模擬執(zhí)行其他操作,在正常情況下他們是同時加1的。在不調(diào)用 stop 方法時,兩個屬性結(jié)果一致,都是3,但是當(dāng)調(diào)用線程 t1 調(diào)用了 stop 方法,屬性 x 值加1了,由于 y 得值還沒來得及加 1 線程停止了,導(dǎo)致本來是原子的操作兩個屬性,最后沒能一起增加,導(dǎo)致了線程安全的問題。
因此 stop 方法在 Java 中標記為過時,并且也不推薦使用了。現(xiàn)在推薦的是使用線程的中斷機制。
3、線程的中斷機制介紹;
Java 中提供的線程中斷機制,實際上是一種線程之間的協(xié)作方式;這種中斷和 stop 有著本質(zhì)的區(qū)別,stop 是直接中斷線程,而 Java 中的中斷機制,其實是給線程設(shè)置一個中斷的標識,當(dāng)前線程并不會被真的終止執(zhí)行,因此需要線程自己去根據(jù)的標識處理相關(guān)邏輯。下面介紹線程中斷機制中,三個重要的方法。
a、interrupt 方法
這個方法可以給線程設(shè)置中斷的標識,然后可以根據(jù)中斷的標識自行處理。例如線程 A 和主線程,主線程調(diào)用線程 A 的 interrupt 方法,這個時候就把線程 A 的中斷標識設(shè)為了 true,但是這個時候線程 A 仍在執(zhí)行,需要線程 A 根據(jù)標識做處理,結(jié)束自己的執(zhí)行。
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(() -> {
while(true) {
System.out.println("threadA is running");
//如果中斷標識為true,則退出循環(huán),threadA 執(zhí)行結(jié)束
if (Thread.currentThread().isInterrupted()) {
System.out.println("threadA is stop");
break;
}
}
});
threadA.start();
Thread.sleep(100);
threadA.interrupt();
threadA.join();
System.out.println("main is end");
}
結(jié)果
threadA is running
threadA is running
threadA is running
threadA is running
threadA is running
threadA is running
threadA is stop
main is end
在調(diào)用 interrupt 方法時,還有一個需要注意的,當(dāng)線程內(nèi)調(diào)用了 wait、sleep、join 方法而被掛起時,當(dāng)前線程會在這些地方拋出,InterruptedException 異常后返回。例如如果線程 A 中有調(diào)用 wait 或者 sleep 或者 join 方法,這個時候主線程調(diào)用了 interrupt 方法,則線程 A 會在調(diào)用這些方法的地方,拋出 InterruptedException 異常,需要程序自己處理該異常,保證線程安全切正確的執(zhí)行。
public static void main(String[] args) throws InterruptedException {
TestObject object = new TestObject(1, 1);
TestRunnable testRunnable = new TestRunnable(object);
Thread t1 = new Thread(testRunnable);
Thread t2 = new Thread(testRunnable);
t1.start();
t2.start();
t1.interrupt();
t1.join();
t2.join();
System.out.println(object.getX() == object.getY());
System.out.println(object.getX() );
System.out.println(object.getY());
System.out.println("main end");
}
private static class TestObject{
private int x ;
private int y ;
TestObject(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
private static class TestRunnable implements Runnable {
private final Object lock = new Object();
private final TestObject object;
TestRunnable(TestObject object) {
this.object = object;
}
@Override
public void run() {
synchronized (lock){
int x = object.getX();
int y = object.getY();
try {
object.setX(x + 1);
Thread.sleep(100);
object.setY(y + 1);
} catch (InterruptedException e) {
object.setY(y);
object.setX(x);
}
}
}
}
結(jié)果
true
2
2
上面示例使用了說明 stop 不安全的例子,稍微修改了代碼,由于示例中有調(diào)用 sleep 方法,因此處理了 InterruptedException 異常,將 stop 改為了 interrupt 方法,從而避免了線程不安全的問題,該示例了沒有對線程的中斷標識做處理,因為該示例處理的意義不大,這個根據(jù)具體使用場景處理就行。
b、isInterrupted 方法
這個方法用于檢查當(dāng)前線程的中斷標識,如果當(dāng)前線程標識為中斷,則返回 true 反之則返回 false,這個方法的使用方式,在上面的示例中已經(jīng)給出,這里就不提供具體的代碼了。
c、interrupted 方法
這個方法也是用于檢查線程的中斷標識,如果中斷則返回 true,反之則是 false。但是和 isInterrupted 方法有兩點不同;第一如果發(fā)現(xiàn)線程被中斷會返回 true,同時會清除線程的中斷標識,這個時候線程標識變成了非中斷;第二這個方法獲取的是當(dāng)前執(zhí)行線程的線程狀態(tài),并不是被調(diào)用線程的狀態(tài),具體看下面的代碼;
/**
* Tests whether the current thread has been interrupted. The
* <i>interrupted status</i> of the thread is cleared by this method. In
* other words, if this method were to be called twice in succession, the
* second call would return false (unless the current thread were
* interrupted again, after the first call had cleared its interrupted
* status and before the second call had examined it).
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if the current thread has been interrupted;
* <code>false</code> otherwise.
* @see #isInterrupted()
* @revised 6.0
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
上面是該方法的 JDK 源碼,它返回的是當(dāng)前執(zhí)行的線程的中斷狀態(tài);
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(() -> {
System.out.println("threadA start interrupted :" + Thread.currentThread().isInterrupted());
while (!Thread.interrupted()) {
}
System.out.println("threadA stop interrupted :" + Thread.currentThread().isInterrupted());
});
threadA.start();
threadA.interrupt();
threadA.join();
System.out.println("main end");
}
結(jié)果
threadA start interrupted :true
threadA stop interrupted :false
main end
總結(jié)
1、stop 是線程不安全的方法,不推薦使用;
2、interrupt 只是設(shè)置一個中斷的標識,需要在線程里面處理相關(guān)邏輯;
3、interrupt 執(zhí)行時,有 sleep、wait、join、的地方會拋出 InterruptedException 異常;
4、interrupted 和 isInterrupted 都可以獲取線程的中斷狀態(tài);
5、interrupted 獲取的是當(dāng)前線程的中斷狀態(tài),并且會清除中斷標識;