騰訊面試官:如何停止一個正在運行的線程?我一臉蒙蔽...

2021-02-17 芋道源碼

停止一個線程意味著在任務處理完任務之前停掉正在做的操作,也就是放棄當前的操作。停止一個線程可以用Thread.stop()方法,但最好不要用它。雖然它確實可以停止一個正在運行的線程,但是這個方法是不安全的,而且是已被廢棄的方法。在java中有以下3種方法可以終止正在運行的線程:

使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。使用stop方法強行終止,但是不推薦這個方法,因為stop和suspend及resume一樣都是過期作廢的方法。

interrupt()方法的使用效果並不像for+break語句那樣,馬上就停止循環。調用interrupt方法是在當前線程中打了一個停止標誌,並不是真的停止線程。

public class MyThread extends Thread {
    public void run(){
        super.run();
        for(int i=0; i<500000; i++){
            System.out.println("i="+(i+1));
        }
    }
}

public class Run {
    public static void main(String args[]){
        Thread thread = new MyThread();
        thread.start();
        try {
            Thread.sleep(2000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

輸出結果:

...
i=499994
i=499995
i=499996
i=499997
i=499998
i=499999
i=500000

Thread.java類中提供了兩種方法:

this.interrupted(): 測試當前線程是否已經中斷;this.isInterrupted(): 測試線程是否已經中斷;

那麼這兩個方法有什麼圖區別呢?我們先來看看this.interrupted()方法的解釋:測試當前線程是否已經中斷,當前線程是指運行this.interrupted()方法的線程。

public class MyThread extends Thread {
    public void run(){
        super.run();
        for(int i=0; i<500000; i++){
            i++;
//            System.out.println("i="+(i+1));
        }
    }
}

public class Run {
    public static void main(String args[]){
        Thread thread = new MyThread();
        thread.start();
        try {
            Thread.sleep(2000);
            thread.interrupt();

            System.out.println("stop 1??" + thread.interrupted());
            System.out.println("stop 2??" + thread.interrupted());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

stop 1??false
stop 2??false

類Run.java中雖然是在thread對象上調用以下代碼:thread.interrupt(), 後面又使用

System.out.println("stop 1??" + thread.interrupted());
System.out.println("stop 2??" + thread.interrupted());

來判斷thread對象所代表的線程是否停止,但從控制臺列印的結果來看,線程並未停止,這也證明了interrupted()方法的解釋,測試當前線程是否已經中斷。這個當前線程是main,它從未中斷過,所以列印的結果是兩個false.

如何使main線程產生中斷效果呢?

public class Run2 {
    public static void main(String args[]){
        Thread.currentThread().interrupt();
        System.out.println("stop 1??" + Thread.interrupted());
        System.out.println("stop 2??" + Thread.interrupted());

        System.out.println("End");
    }
}

運行效果為:

stop 1??true
stop 2??false
End

方法interrupted()的確判斷出當前線程是否是停止狀態。但為什麼第2個布爾值是false呢? 官方幫助文檔中對interrupted方法的解釋:測試當前線程是否已經中斷。線程的中斷狀態由該方法清除。 換句話說,如果連續兩次調用該方法,則第二次調用返回false。

下面來看一下inInterrupted()方法。

public class Run3 {
    public static void main(String args[]){
        Thread thread = new MyThread();
        thread.start();
        thread.interrupt();
        System.out.println("stop 1??" + thread.isInterrupted());
        System.out.println("stop 2??" + thread.isInterrupted());
    }
}

運行結果:

stop 1??true
stop 2??true

isInterrupted()並為清除狀態,所以列印了兩個true。

有了前面學習過的知識點,就可以在線程中用for語句來判斷一下線程是否是停止狀態,如果是停止狀態,則後面的代碼不再運行即可:

public class MyThread extends Thread {
    public void run(){
        super.run();
        for(int i=0; i<500000; i++){
            if(this.interrupted()) {
                System.out.println("線程已經終止, for循環不再執行");
                break;
            }
            System.out.println("i="+(i+1));
        }
    }
}

public class Run {
    public static void main(String args[]){
        Thread thread = new MyThread();
        thread.start();
        try {
            Thread.sleep(2000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

...
i=202053
i=202054
i=202055
i=202056
線程已經終止, for循環不再執行

上面的示例雖然停止了線程,但如果for語句下面還有語句,還是會繼續運行的。看下面的例子:

public class MyThread extends Thread {
    public void run(){
        super.run();
        for(int i=0; i<500000; i++){
            if(this.interrupted()) {
                System.out.println("線程已經終止, for循環不再執行");
                break;
            }
            System.out.println("i="+(i+1));
        }

        System.out.println("這是for循環外面的語句,也會被執行");
    }
}

使用Run.java執行的結果是:

...
i=180136
i=180137
i=180138
i=180139
線程已經終止, for循環不再執行
這是for循環外面的語句,也會被執行

如何解決語句繼續運行的問題呢? 看一下更新後的代碼:

public class MyThread extends Thread {
    public void run(){
        super.run();
        try {
            for(int i=0; i<500000; i++){
                if(this.interrupted()) {
                    System.out.println("線程已經終止, for循環不再執行");
                        throw new InterruptedException();
                }
                System.out.println("i="+(i+1));
            }

            System.out.println("這是for循環外面的語句,也會被執行");
        } catch (InterruptedException e) {
            System.out.println("進入MyThread.java類中的catch了。。。");
            e.printStackTrace();
        }
    }
}

使用Run.java運行的結果如下:

...
i=203798
i=203799
i=203800
線程已經終止, for循環不再執行
進入MyThread.java類中的catch了。。。
java.lang.InterruptedException
 at thread.MyThread.run(MyThread.java:13)

如果線程在sleep()狀態下停止線程,會是什麼效果呢?

public class MyThread extends Thread {
    public void run(){
        super.run();

        try {
            System.out.println("線程開始。。。");
            Thread.sleep(200000);
            System.out.println("線程結束。");
        } catch (InterruptedException e) {
            System.out.println("在沉睡中被停止, 進入catch, 調用isInterrupted()方法的結果是:" + this.isInterrupted());
            e.printStackTrace();
        }

    }
}

使用Run.java運行的結果是:

線程開始。。。
在沉睡中被停止, 進入catch, 調用isInterrupted()方法的結果是:false
java.lang.InterruptedException: sleep interrupted
 at java.lang.Thread.sleep(Native Method)
 at thread.MyThread.run(MyThread.java:12)

從列印的結果來看, 如果在sleep狀態下停止某一線程,會進入catch語句,並且清除停止狀態值,使之變為false。

前一個實驗是先sleep然後再用interrupt()停止,與之相反的操作在學習過程中也要注意:

public class MyThread extends Thread {
    public void run(){
        super.run();
        try {
            System.out.println("線程開始。。。");
            for(int i=0; i<10000; i++){
                System.out.println("i=" + i);
            }
            Thread.sleep(200000);
            System.out.println("線程結束。");
        } catch (InterruptedException e) {
             System.out.println("先停止,再遇到sleep,進入catch異常");
            e.printStackTrace();
        }

    }
}

public class Run {
    public static void main(String args[]){
        Thread thread = new MyThread();
        thread.start();
        thread.interrupt();
    }
}

運行結果:

i=9998
i=9999
先停止,再遇到sleep,進入catch異常
java.lang.InterruptedException: sleep interrupted
 at java.lang.Thread.sleep(Native Method)
 at thread.MyThread.run(MyThread.java:15)

使用stop()方法停止線程則是非常暴力的。

public class MyThread extends Thread {
    private int i = 0;
    public void run(){
        super.run();
        try {
            while (true){
                System.out.println("i=" + i);
                i++;
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String args[]) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
        Thread.sleep(2000);
        thread.stop();
    }
}

運行結果:

i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9

Process finished with exit code 0

調用stop()方法時會拋出java.lang.ThreadDeath異常,但是通常情況下,此異常不需要顯示地捕捉。

public class MyThread extends Thread {
    private int i = 0;
    public void run(){
        super.run();
        try {
            this.stop();
        } catch (ThreadDeath e) {
            System.out.println("進入異常catch");
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String args[]) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
    }
}

stop()方法以及作廢,因為如果強制讓線程停止有可能使一些清理性的工作得不到完成。另外一個情況就是對鎖定的對象進行了解鎖,導致數據得不到同步的處理,出現數據不一致的問題。

使用stop()釋放鎖將會給數據造成不一致性的結果。如果出現這樣的情況,程序處理的數據就有可能遭到破壞,最終導致程序執行的流程錯誤,一定要特別注意:

public class SynchronizedObject {
    private String name = "a";
    private String password = "aa";

    public synchronized void printString(String name, String password){
        try {
            this.name = name;
            Thread.sleep(100000);
            this.password = password;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

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

public class MyThread extends Thread {
    private SynchronizedObject synchronizedObject;
    public MyThread(SynchronizedObject synchronizedObject){
        this.synchronizedObject = synchronizedObject;
    }

    public void run(){
        synchronizedObject.printString("b", "bb");
    }
}

public class Run {
    public static void main(String args[]) throws InterruptedException {
        SynchronizedObject synchronizedObject = new SynchronizedObject();
        Thread thread = new MyThread(synchronizedObject);
        thread.start();
        Thread.sleep(500);
        thread.stop();
        System.out.println(synchronizedObject.getName() + "  " + synchronizedObject.getPassword());
    }
}

輸出結果:

b  aa

由於stop()方法以及在JDK中被標明為「過期/作廢」的方法,顯然它在功能上具有缺陷,所以不建議在程序張使用stop()方法。

將方法interrupt()與return結合使用也能實現停止線程的效果:

public class MyThread extends Thread {
    public void run(){
        while (true){
            if(this.isInterrupted()){
                System.out.println("線程被停止了!");
                return;
            }
            System.out.println("Time: " + System.currentTimeMillis());
        }
    }
}

public class Run {
    public static void main(String args[]) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }
}

輸出結果:

...
Time: 1467072288503
Time: 1467072288503
Time: 1467072288503
線程被停止了!

不過還是建議使用「拋異常」的方法來實現線程的停止,因為在catch塊中還可以將異常向上拋,使線程停止事件得以傳播。

相關焦點

  • 阿里面試官問我Java線程和作業系統線程什麼關係
    這個問題是安琪拉之前面試被問到的一個問題,正好順著上一篇文章介紹完線程調用時的用戶態和內核態的切換,後續把Java 並發的都一起講了。面試官:聽前一個面試官說你Java並發這塊掌握的不錯,我們深入的交流一下;我:  看了看面試官頭部稀疏的結締組織,已然覺得這場面試不簡單,不過好在事前把安琪拉的博客看了個遍,有所準備,我回答說:咳咳,掌握的還算可以。
  • 為什麼校招面試中總被問「線程與進程的區別」?我該如何回答?
    線程是進程的一個實體,是獨立運行和獨立調度的基本單位(CPU上真正運行的是線程)。線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。
  • 面試官:不使用synchronized和lock,如何實現一個線程安全的單例?
    稍微了解一點單例的朋友也都知道實現單例是要考慮並發問題的,一般情況下,我們都會使用synchronized來保證線程安全。那麼,如果有這樣一道面試題:不使用synchronized和lock,如何實現一個線程安全的單例?你該如何回答?C類應聘者:可以使用餓漢模式實現單例。
  • Java 線程面試題 Top 50
    僅僅知道線程的基本概念是遠遠不夠的, 你必須知道如何處理死鎖,競態條件,內存衝突和線程安全等並發問題。掌握了這些技巧,你就可以輕鬆應對多線程和並發麵試了。許多Java程式設計師在面試前才會去看面試題,這很正常。因為收集面試題和練習很花時間,所以我從許多面試者那裡收集了Java多線程和並發相關的50個熱門問題。我只收集了比較新的面試題且沒有提供全部答案。
  • Java:一個重要的停止線程方法——interrupt
    = null && nowThread.isAlive()){ LOG.info("當前存在正在執行的線程,本次線程不執行,請稍後再試"); return;}else{ threadMap.put("nowThread",Thread.currentThread());}.
  • 面試前必看Java線程面試題
    如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。一個線程安全的計數器類的同一個實例對象在被多個線程使用的情況下也不會出現計算失誤。很顯然你可以將集合類分成兩組,線程安全和非線程安全的。
  • 一道面試題,我把阿里面試官唬住了
    這個問題是安琪拉之前面試被問到的一個問題,正好順著上一篇文章介紹完線程調用時的用戶態和內核態的切換,後續把 Java 並發的都一起講了。面試官:聽前一個面試官說你 Java 並發這塊掌握的不錯,我們深入的交流一下;我:  看了看面試官頭部稀疏的結締組織,已然覺得這場面試不簡單,不過好在事前把安琪拉的博客看了個遍,有所準備,我回答說:咳咳,掌握的還算可以。
  • 某產品吐槽騰訊:面試官不專業,上午評估面試作業,下午停止招聘
    某產品吐槽騰訊:面試官不專業,上午評估面試作業,下午停止招聘!騰訊作為中國網際網路最大的網絡公司之一,其一舉一動都影響了廣大網民,騰訊的日益壯大,在社交金字塔的頂端,有著不可撼動的地位,因此,騰訊公司也吸引了很多人才的加入,但近幾年的騰訊,一直都不和諧的聲音出現,很多關於騰訊不專業的事情比比皆是。
  • 面試官讓我講下線程的 WAITING 狀態,我笑了
    面試官Q:你講下線程狀態中的WAITING狀態,什麼時候會處於這個狀態?什麼時候離開這個狀態?小菜J 會心一笑...一個正在無限期等待另一個線程執行一個特別的動作的線程處於WAITING狀態。一個調用了 Thread.join 方法的線程會等待指定的線程結束。
  • 深度好文|面試官:進程和線程,我只問這19個問題
    進程是一個正在執行程序的實例,包括程序計數器、寄存器和程序變量的當前值。簡單來說進程就是一個程序的執行流程,內部保存程序運行所需的資源。在作業系統中可以有多個進程在運行,可對於CPU來說,同一時刻,一個CPU只能運行一個進程,但在某一時間段內,CPU將這一時間段拆分成更短的時間片,CPU不停的在各個進程間遊走,這就給人一種並行的錯覺,像CPU可以同時運行多個進程一樣,這就是偽並行。02 進程和程序有什麼聯繫?
  • 面試官:你工作3年了,ArrayList是線程不安全都沒掌握,不應該啊
    這個類我們平時接觸得最多的一個列表集合類。面試時,也有不少面試官會針對此知識點考察求職者。小愛最近又去面試了,最近到某知名網際網路公司面試,做了筆試題後,面試官剛好問ArrayList是線程安全還是非線程安全?
  • 2017騰訊實習生Android客戶端開發面試總結
    面試官:似乎很滿意,接著問道,那Android中是如何避免ANR的?我:這些問題因為都是很基礎的,並且面試前複習過了,所以對答如流。我說我舉一種方法吧,例如AsyncTask,具體介紹了他的doInBackground和updateProgress以及postExecute三個方法的使用以及參數的類型轉換,還分析了AsyncTask的缺點,就是它所維護的線程池大小為128,同一時刻只能有5個工作線程和一個緩存線程,如果耗時操作工作量巨大就會導致線程池大小不夠用,這就是它的缺點,另外我還介紹了它的解決方式,就是由一個控制線程來處理
  • 「秒殺」面試官,你只差這10道Python面試題
    今天狗哥請我來幫一位讀者解決面試的問題,其實我們都是從這個階段過來的。所以能幫上大家也是比較開心的。那今天我從面試的角度給大家歸納下 Django 工程師面試時面試官都會問些啥。其實解析這些問題之前,大家要明白一個基本原則:面試的目的是找到合適的候選人,提問的目的是為了考察當前候選人對當前崗位的匹配度。
  • JAVA面試題及答案一百道(SE篇上)——老面試官的經驗之談
    上篇請看這裡-->JAVA面試題及答案一百道(SE篇上)——老面試官的經驗之談本文的面試題裡有部分關於設計模式的題目,但是阿偉要在這裡提醒各位一句話,找工作時面試官的水平可以清晰地反映出這個公司的水平,如果你的面試官抓住設計模式死摳死問,想讓你一成不變的把網上的面試答案背下來
  • 面試官問我:線程池中多餘的線程是如何回收的?
    不過,我倒是對線程池是如何回收工作線程比較感興趣,所以簡單分析了一下,加深對線程池的理解吧。下面以JDK1.8為例進行分析1. runWorker(Worker w)工作線程啟動後,就進入runWorker(Worker w)方法。
  • 面試官一步一步的套路你,為什麼SimpleDateFormat不是線程安全的
    面試官:能寫一下你是如何使用的嗎?使用ThreadLocal包裝一下,每個線程都有自己的SimpleDateFormat實例對象,這樣多線程並發的情況下就不會出現線程不安全的問題了。面試官:那為什麼SimpleDateFormat作為共享變量會出現線程不安全的問題?小小白:以下面的代碼為例,說明一下原因。
  • Java經典面試題:一個線程兩次調用start()方法會出現什麼情況?
    對Java面試題感興趣的朋友,可以拉到文末,掃碼或者點擊「閱讀原文」訂閱我的專欄。今天我要問你的問題是,一個線程兩次調用start()方法會出現什麼情況?談談線程的生命周期和狀態轉移。典型回答Java的線程是不允許啟動兩次的,第二次調用必然會拋出IllegalThreadStateException,這是一種運行時異常,多次調用start被認為是編程錯誤。
  • 很多面試官相信的謠言,作為面試者,你該發聲了!一道題角色逆轉
    本文轉載自【微信公眾號:小碼逆襲,ID:gh_7c5a039380a0】經微信公眾號授權轉載,如需轉載與原文作者聯繫情景再現今天一個學妹跟我分享她今天的面試過程,她面試的是java開發崗,跟我說了面試官大概問了哪些問題,基本都是一些常規的基本知識
  • 我和面試官的博弈:Java 並發編程篇
    面試官:你先說下你對synchronized的了解。我:synchronized可以保證方法或者代碼在運行時,同一時刻只有一個方法可以進入到臨界區,同時還可以保證共享變量的內存可見性。面試官:你剛提到了每個對象都有一個monitor與之對應,那什麼是Monitor呢?我:我們可以把它理解為一個同步工具,也可以描述為一種同步機制,它通常被描述為一個對象。
  • 面試官說我的簡歷像包裝的,面試後面試官向我道歉了
    小編的一個朋友入職現公司一年半了,領導絲毫沒有要給他漲工資的想法。這不,馬上趕上金三銀四的黃金跳槽階段,小編的朋友就去試了試水,結果鎩羽而歸,找我一頓吐槽。下面小編就給大家分享一段面試官與我朋友的面試內容 ▼面試官:你剛才提到的xxx項目,能講一下具體細節以及你負責的模塊嗎