多執行緒併發篇——如何停止執行緒

追風少年10發表於2019-03-03

  筆者是廣州的java程式設計師,剛畢業半年,工作之餘寫部落格,如果覺得我的文章寫得不錯,可以關注我的微信公眾號(J2彬彬),裡面會有更多精彩內容。從2018年8月份開始寫部落格,希望日後寫出更多通俗易懂的技術文章與大家一同分享。

前言

  你有沒有想過,如何停止一個執行緒?很多人首先會想到Thread.stop()方法,但是這個方法已經過時,不推薦使用,因為這個方法會帶來安全問題,什麼安全問題呢?後面會有詳細說明。我們先講講目前JDK API推薦使用停止執行緒的方法Thread.interrupt()方法。

Thread.interrupt()

  既然不能直接stop執行緒,那麼只有一種方法可以讓執行緒結束,那就是讓run方法運結束。
  Thread.interrupt()代表的意思是“停止,中止”。但是這個方法需要加入一個判斷才可以完成執行緒的停止。一旦檢測到執行緒處於中斷狀態,那麼就有機會結束run方法。
  下面以一個程式碼示例看看它是如何停止執行緒的?

一、interrupt()停止執行緒
package com.bingo.thread.stopThread;

/**
 * Created with IntelliJ IDEA.
 * Description: 停止執行緒不推薦使用stop方法,此方法不安全,我們可以使用Thread.interrupt()
 * User: bingo
 */
public class Run {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread.interrupt();
        System.out.println("end...");
    }
}


class MyThread extends Thread{

    @Override
    public void run() {
        super.run();

        for (int i = 0; i < 10000 ; i++) {

            if(this.isInterrupted()){
                System.out.println("已經是停止狀態了,我要退出了");
                break;
            }
            System.out.println("i="+(i+1));
        }
    }
}
複製程式碼

執行結果:

i=1
......
i=3102
i=3103
i=3104
i=3105
i=3106
i=3107
i=3108
end...
已經是停止狀態了,我要退出了
複製程式碼

  執行結果我們可以看到,當myThread執行緒的迴圈執行到i=3108的時候,由於執行緒被中斷,而跳出迴圈,這個例子很好詮釋了interrupt方法的作用。

二、interrupt可以清除執行緒的凍結狀態,讓執行緒恢復到可執行的狀態上來
package com.bingo.thread.stopThread;

/**
 * Created with IntelliJ IDEA.
 * Description: interrupt可以清除執行緒的凍結狀態,讓執行緒恢復到可執行的狀態上來。
 * User: bingo
 */
public class Run2 {

    public static void main(String[] args) {
        MyThread2 thread = new MyThread2();
        thread.start();
        thread.interrupt();
        System.out.println("main end...");
    }
}

class MyThread2 extends Thread{

    @Override
    public void run() {
        System.out.println("run begin...");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            System.out.println("run 在沉睡中被中止,進入catch");
            e.printStackTrace();
        }
        System.out.println("run end...");
    }
}
複製程式碼

執行結果:

main end...
run begin...
run 在沉睡中被中止,進入catch
run end...
java.lang.InterruptedException: sleep interrupted
 at java.lang.Thread.sleep(Native Method)
 at com.bingo.thread.stopThread.MyThread2.run(Run2.java:26)
複製程式碼

  從執行結果我們可以看到,本來run方法睡眠時間為1000秒,但是列印結果卻是瞬間的,其實sleep已經被interrupt方法給打斷,此時執行緒凍結狀態被清除,並丟擲異常,被catch捕獲,列印異常資訊。

暴力停止——Stop

  下圖是JDK API對stop方法的描述,可以看到已過時,不推薦使用,並告訴我們此方法為何不安全?

多執行緒併發篇——如何停止執行緒

  如果某個執行緒加鎖,stop方法停止該執行緒時會把鎖釋放掉,可能造成資料不一致的情況。下面程式碼可以說明此問題。

package com.bingo.thread.stopThread;

/**
 * Created with IntelliJ IDEA.
 * Description: stop()方法為何不安全?下面例子可解答
 * User: bingo
 */
public class StopTest {

    public static void main(String[] args) {

        try {

            SynchrionzedObject object = new SynchrionzedObject();
            MyThread3 t = new MyThread3(object);
            t.start();

            Thread.sleep(500);

            t.stop();

            System.out.println(object.getUsername()+" "+object.getPassword());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class SynchrionzedObject{

    private String username = "a";

    private String password = "aa";

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public synchronized void printString(String username,String password){
        try {

            this.username = username;
            Thread.sleep(10000);
            this.password = password;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

class MyThread3 extends Thread{

    private SynchrionzedObject object;

    public MyThread3(SynchrionzedObject object){
        this.object = object;
    }

    @Override
    public void run() {
        object.printString("b", "bb");
    }
}

複製程式碼

執行結果:

b aa
複製程式碼

  從上面例子我們可以看到雖然printString方法加了鎖,但是run方法執行過程中突然被stop了,鎖被釋放,MyThread執行緒只對username進行了賦值,而password賦值動作未執行,此時造成資料不一致。

最後

  其實java多執行緒很多方法內部都是native方法,也就是基於JVM內部實現的,所以我們有必要結合JVM一起學習這部分的內容。技術的進步需要每個小小的積累,才能走得更遠,更長久。

相關文章