如何停止線程

前言

線程的使用想必大家都很熟悉铛碑,那么關于如何停止一個線程,或者說如何正確的停止一個線程虽界,尤其是在涉及到鎖汽烦、多個線程需要交互的情況,應該如何去停止線程來保證程序的行為符合自己的預期呢莉御?

停止線程撇吞,我覺得首先需要明確自己想要停止線程的'停止'到底需要線程干什么俗冻。是不再需要線程執(zhí)行任務了,讓線程直接TERMINATED,還是只是需要線程暫時掛起一段時間牍颈,比如WAITINGTIMED_WAITING迄薄,等符合條件之后再被喚醒繼續(xù)執(zhí)行。如果不太了解TERMINATED煮岁、WAITINGTIMED_WAITING指的是什么噪奄,可以先看看Java線程的狀態(tài)這篇文章。

  • 在這討論的是如何讓線程終止自己的任務人乓,并且能夠正確的釋放資源、不會對與其交互的線程產生預期之外的影響

線程停止的方法

Java中的Thread提供了停止線程和掛起線程的方法都毒,但是它們都被廢棄了

Deprecated: This method was originally designed to force a thread to stop and throw a ThreadDeath as an exception. It was inherently unsafe. Stopping a thread with Thread.stop causes it to unlock all of the monitors that it has locked (as a natural consequence of the unchecked ThreadDeath exception propagating up the stack). If any of the objects previously protected by these monitors were in an inconsistent state, the damaged objects become visible to other threads, potentially resulting in arbitrary behavior. Many uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running. If the target thread waits for long periods (on a condition variable, for example), the interrupt method should be used to interrupt the wait. For more information, see Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?

  • 從官方文檔的描述中我們可以看到色罚,stop本身是不安全的。當調用stop方法去停止線程時账劲,線程會立即釋放它持有的所有監(jiān)視器(monitors)鎖戳护,受監(jiān)視器鎖住的對象對其它線程就會變得可見。由于執(zhí)行這個停止操作是隨時的瀑焦,不能保證操作的原子性腌且,線程在執(zhí)行某些任務的中途就退出了,比如寫入操作榛瓮,寫到一半就直接被停止了铺董。那么被寫入的對象就會處于不一致的狀態(tài)。此時該線程就留下了一個爛攤子,但是它已經停止了禀晓,沒有人再去處理這個爛攤子了精续。這個時候有另外一個線程再去訪問該對象,就可能會出現預期之外的結果粹懒。另外停止的時候重付,對于IO操作,停止的時候也沒有機會去關閉流凫乖。所以不能直接使用stop()停止一個線程

  • suspend()

Deprecated: This method was designed to suspend the Thread but it was inherently deadlock-prone. If the target thread holds a lock on the monitor protecting a critical system resource when it is suspended, no thread can access this resource until the target thread is resumed. If the thread that would resume the target thread attempts to lock this monitor prior to calling resume, deadlock results. Such deadlocks typically manifest themselves as "frozen" processes. For more information, see Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?

  • 對于suspend方法确垫,掛起線程,配合resumed方法一起使用帽芽∩鞠疲可以達到掛起和恢復線程運行的目的。但是suspend也被廢棄了嚣镜。這是為什么呢爬迟?首先第一點,如果使用不當菊匿,先執(zhí)行了resumed付呕,才suspend计福。那么線程就被一直掛起了。第二徽职,suspend的線程不會釋放持有的鎖象颖。如果線程A持有鎖lockA,然后被suspend,此時線程B要resumed線程A姆钉,但是在resumed之前需要獲取鎖lockA说订,這就會導致死鎖。這么容易導致出現問題潮瓶,suspend被廢棄也就不難理解了陶冷。

  • 關于stopsuspend被廢棄的更多信息可以看 Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?

線程停止的方法

  • 既然線程停止的方法都被廢棄了毯辅,那么應該如何讓線程停止呢埂伦?實際上在stop方法的注釋中給我們提到了

Many uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running. If the target thread waits for long periods (on a condition variable, for example), the interrupt method should be used to interrupt the wait.

簡單翻譯下就是可以通過修改一些變量以指示目標線程應該停止運行,目標線程應該定期檢查這個變量思恐,如果該變量表示它將停止運行沾谜,則以有序的方式從其run方法返回。如果目標線程等待很長時間(例如胀莹,在一個條件變量上)基跑,應該使用interrupt方法來中斷等待。

Unless the current thread is interrupting itself, which is always permitted, the checkAccess method of this thread is invoked, which may cause a SecurityException to be thrown.

If this thread is blocked in an invocation of the Object#wait(), Object#wait(long), or Object#wait(long, int) methods of the Object class, or of the join(), join(long), join(long,int), sleep(long), or sleep(long,int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

If this thread is blocked in an I/O operation upon an then the channel will be closed, the thread's interrupt status will be set, and the thread will receive a .

If this thread is blocked in a java.nio.channels.Selector then the thread's interrupt status will be set and it will return immediately from the selection operation, possibly with a non-zero value, just as if the selector's wakeup() method were invoked.

If none of the previous conditions hold then this thread's interrupt status will be set.

Interrupting a thread that is not alive need not have any effect.

Java線程提供了中斷方法interrupt(),當然它不是真正的打斷線程的運行描焰,它是native方法媳否,原理就是利用一個標記記錄線程的中斷狀態(tài),也就是記錄線程有沒有被其它線程執(zhí)行了中斷操作栈顷。調用它僅僅只是為線程打了一個中斷的標記逆日。而線程可以通過靜態(tài)方法Thread.interrupted() 或成員方法isInterrupted()來感知其它線程對自己的中斷操作從而作出相應的響應

注釋中也說明了,當線程處于WAITING,TIMED_WAITING或在進行I/O操作阻塞時萄凤,調用interrupt()會接收到一個InterruptedException,并且中斷狀態(tài)也會被清除

class ThreadTest {

    @Test
    fun test() {
        val thread = MyThread("thread 1")

        thread.start()

        thread.interrupt()

        println("thread.isInterrupted():${thread.isInterrupted()}")

        Thread.sleep(6000)
        println("thread state:${thread.getState()}")
        println("主線程等待6s結束")
    }


    class MyThread constructor(name: String) :
        Thread(name) {
        var count = 0
        override fun run() {
            super.run()
            while (true) {
                //do sth
                if (isInterrupted()) {
                    count++
                    if (count < 5) {
                        println("中斷狀態(tài):${isInterrupted()}")
                    }else{
                        println("結束線程")
                        break
                    }
                }
            }
        }
    }
}
  • 結果如下
中斷并結束.png
class ThreadTest {

    @Test
    fun test() {
        val thread = MyThread("thread 1")

        thread.start()

        thread.interrupt()

        println("thread.isInterrupted():${thread.isInterrupted()}")

        Thread.sleep(6000)
        println("thread state:${thread.getState()}")
        println("主線程等待6s結束")
    }


    class MyThread constructor(name: String) :
        Thread(name) {
        var count = 0
        override fun run() {
            super.run()
            while (true) {
                //do sth
                try {
                    sleep(500)
                    if (isInterrupted()) {
                        count++
                        if (count < 5) {
                            println("中斷狀態(tài):${isInterrupted()}")
                        }else{
                            println("結束線程")
                            break
                        }
                    }
                }catch (e:InterruptedException){
                    println("接收到 InterruptedException室抽,中斷狀態(tài):${isInterrupted()}")
                }

            }
        }
    }
}
  • 結果如下


    接收異常中斷狀態(tài)被清除.png
  • 可以看到調用thread.interrupt()之后,成員方法isInterrupted()就可以感知到中斷操作靡努,如果是被阻塞坪圾,調用時會拋出一個InterruptedException,中斷狀態(tài)隨之清除

isInterrupted()和interrupted()的區(qū)別

前面提到成員方法isInterrupted()和靜態(tài)方法interrupted()都能感知到中斷操作。那么它們之間有什么區(qū)別惑朦?

  • 先來看下面的例子
class ThreadTest {

    @Test
    fun test() {
        val thread = MyThread("thread 1")

        thread.start()


        thread.interrupt()

        println("thread.isInterrupted():${thread.isInterrupted()}")

        Thread.sleep(6000)
        println("thread state:${thread.getState()}")
        println("主線程等待6s結束")
    }


    class MyThread constructor(name: String) :
        Thread(name) {
        var count = 0
        override fun run() {
            super.run()
            while (true) {
                //do sth
                count++
                if (count < 5) {
                    println("中斷狀態(tài) isInterrupted():${isInterrupted()}")
                    println("中斷狀態(tài) interrupted():${Thread.interrupted()}")
                } else {
                    println("結束線程")
                    break
                }
            }
        }
    }
}
  • 結果如下


    測試區(qū)別結果.png

乍一看好像也沒有區(qū)別兽泄,輸出的結果都是一致的,實際上這也正是它們的區(qū)別導致的漾月。通過前面的例子病梢,我們可以發(fā)現,執(zhí)行了中斷操作后,多次調用isInterrupted()的返回結果一直返回true蜓陌,當然拋InterruptedException之后由于中斷狀態(tài)被清除觅彰,isInterrupted()的返回結果為false。而執(zhí)行了中斷操作后钮热,第一次調用interrupted()的結果為true填抬,而且調用interrupted()也會清除中斷狀態(tài),所以之后的中斷狀態(tài)一直為false隧期,只有再次執(zhí)行中斷操作飒责,才會返回true

在了解了兩者的區(qū)別之后,針對不同的需求才能更好的選擇使用哪個方法來監(jiān)聽中斷狀態(tài)

打中斷標記

官方提供的中斷方法是native的仆潮,我們知道jni調用多多少少還是有一點性能上的消耗的宏蛉。所以我們可以自己給線程定義一個中斷標記

class ThreadTest {

    @Test
    fun test() {
        val thread = MyThread("thread 1")

        thread.start()

        Thread.sleep(2)
        thread.stop = true
        Thread.sleep(6000)
        println("thread state:${thread.getState()}")
        println("主線程等待6s結束")
    }


    class MyThread constructor(name: String) :
        Thread(name) {
        //volatile保證可見性
        @Volatile var  stop = false
        override fun run() {
            super.run()
            while (true) {
                if (stop){
                    break
                }
                println("do sth")
            }

            println("線程結束")
        }
    }
}
中斷標記.png
  • 對于系統(tǒng)提供的方法和自己打標記的方法又應該如何選擇呢?實際上可以看到系統(tǒng)提供的interrupt()方法對于長時間掛起的線程性置,比如被Object.wait檐晕、Thread.sleep等方法阻塞住的線程也可以正常的停止。而如果是自己打標記的蚌讼,如果線程被阻塞了,就無法正常響應達到停止的目的了个榕。所以如果想要支持阻塞也能響應篡石,就應選擇系統(tǒng)提供的interrupt()方法,如果不需要支持西采,則更推薦使用自己打中斷標記凰萨,從性能上來說會更加好一點。
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末械馆,一起剝皮案震驚了整個濱河市胖眷,隨后出現的幾起案子,更是在濱河造成了極大的恐慌霹崎,老刑警劉巖珊搀,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異尾菇,居然都是意外死亡境析,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門派诬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劳淆,“玉大人,你說我怎么就攤上這事默赂∨嫱遥” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵缆八,是天一觀的道長曲掰。 經常有香客問我疾捍,道長,這世上最難降的妖魔是什么蜈缤? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任拾氓,我火速辦了婚禮,結果婚禮上底哥,老公的妹妹穿的比我還像新娘咙鞍。我一直安慰自己,他們只是感情好趾徽,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布续滋。 她就那樣靜靜地躺著,像睡著了一般孵奶。 火紅的嫁衣襯著肌膚如雪疲酌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天了袁,我揣著相機與錄音朗恳,去河邊找鬼。 笑死载绿,一個胖子當著我的面吹牛粥诫,可吹牛的內容都是我干的。 我是一名探鬼主播崭庸,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼怀浆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了怕享?” 一聲冷哼從身側響起执赡,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎函筋,沒想到半個月后沙合,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡跌帐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年灌诅,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片含末。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡猜拾,死狀恐怖,靈堂內的尸體忽然破棺而出佣盒,到底是詐尸還是另有隱情挎袜,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站盯仪,受9級特大地震影響紊搪,放射性物質發(fā)生泄漏。R本人自食惡果不足惜全景,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一耀石、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧爸黄,春花似錦滞伟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至称开,卻和暖如春亩钟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鳖轰。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工清酥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蕴侣。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓总处,卻偏偏與公主長得像,于是被迫代替她去往敵國和親睛蛛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容