前言
線程的使用想必大家都很熟悉铛碑,那么關于如何停止一個線程,或者說如何正確的停止一個線程虽界,尤其是在涉及到鎖汽烦、多個線程需要交互的情況,應該如何去停止線程來保證程序的行為符合自己的預期呢莉御?
停止線程撇吞,我覺得首先需要明確自己想要停止線程的'停止'到底需要線程干什么俗冻。是不再需要線程執(zhí)行任務了,讓線程直接TERMINATED
,還是只是需要線程暫時掛起一段時間牍颈,比如WAITING
或TIMED_WAITING
迄薄,等符合條件之后再被喚醒繼續(xù)執(zhí)行。如果不太了解TERMINATED
煮岁、WAITING
或TIMED_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 uncheckedThreadDeath
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 ofstop
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), theinterrupt
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()
停止一個線程
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
被廢棄也就不難理解了陶冷。關于
stop
、suspend
被廢棄的更多信息可以看 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), theinterrupt
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
}
}
}
}
}
}
- 結果如下
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()}")
}
}
}
}
}
-
結果如下
可以看到調用
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ū)別兽泄,輸出的結果都是一致的,實際上這也正是它們的區(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("線程結束")
}
}
}
- 對于系統(tǒng)提供的方法和自己打標記的方法又應該如何選擇呢?實際上可以看到系統(tǒng)提供的
interrupt()
方法對于長時間掛起的線程性置,比如被Object.wait
檐晕、Thread.sleep
等方法阻塞住的線程也可以正常的停止。而如果是自己打標記的蚌讼,如果線程被阻塞了,就無法正常響應達到停止的目的了个榕。所以如果想要支持阻塞也能響應篡石,就應選擇系統(tǒng)提供的interrupt()
方法,如果不需要支持西采,則更推薦使用自己打中斷標記凰萨,從性能上來說會更加好一點。