如何停止线程

前言

线程的使用想必大家都很熟悉,那么关于如何停止一个线程,或者说如何正确的停止一个线程,尤其是在涉及到锁、多个线程需要交互的情况,应该如何去停止线程来保证程序的行为符合自己的预期呢?

停止线程,我觉得首先需要明确自己想要停止线程的'停止'到底需要线程干什么。是不再需要线程执行任务了,让线程直接TERMINATED,还是只是需要线程暂时挂起一段时间,比如WAITINGTIMED_WAITING,等符合条件之后再被唤醒继续执行。如果不太了解TERMINATEDWAITINGTIMED_WAITING指的是什么,可以先看看Java线程的状态这篇文章。

  • 在这讨论的是如何让线程终止自己的任务,并且能够正确的释放资源、不会对与其交互的线程产生预期之外的影响

线程停止的方法

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方法去停止线程时,线程会立即释放它持有的所有监视器(monitors)锁,受监视器锁住的对象对其它线程就会变得可见。由于执行这个停止操作是随时的,不能保证操作的原子性,线程在执行某些任务的中途就退出了,比如写入操作,写到一半就直接被停止了。那么被写入的对象就会处于不一致的状态。此时该线程就留下了一个烂摊子,但是它已经停止了,没有人再去处理这个烂摊子了。这个时候有另外一个线程再去访问该对象,就可能会出现预期之外的结果。另外停止的时候,对于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也被废弃了。这是为什么呢?首先第一点,如果使用不当,先执行了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方法,原理就是利用一个标记记录线程的中断状态,也就是记录线程有没有被其它线程执行了中断操作。调用它仅仅只是为线程打了一个中断的标记。而线程可以通过静态方法Thread.interrupted() 或成员方法isInterrupted()来感知其它线程对自己的中断操作从而作出相应的响应

注释中也说明了,当线程处于WAITING,TIMED_WAITING或在进行I/O操作阻塞时,调用interrupt()会接收到一个InterruptedException,并且中断状态也会被清除

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("中断状态:${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("中断状态:${isInterrupted()}")
                        }else{
                            println("结束线程")
                            break
                        }
                    }
                }catch (e:InterruptedException){
                    println("接收到 InterruptedException,中断状态:${isInterrupted()}")
                }

            }
        }
    }
}
  • 结果如下


    接收异常中断状态被清除.png
  • 可以看到调用thread.interrupt()之后,成员方法isInterrupted()就可以感知到中断操作,如果是被阻塞,调用时会抛出一个InterruptedException,中断状态随之清除

isInterrupted()和interrupted()的区别

前面提到成员方法isInterrupted()和静态方法interrupted()都能感知到中断操作。那么它们之间有什么区别?

  • 先来看下面的例子
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("中断状态 isInterrupted():${isInterrupted()}")
                    println("中断状态 interrupted():${Thread.interrupted()}")
                } else {
                    println("结束线程")
                    break
                }
            }
        }
    }
}
  • 结果如下


    测试区别结果.png

乍一看好像也没有区别,输出的结果都是一致的,实际上这也正是它们的区别导致的。通过前面的例子,我们可以发现,执行了中断操作后,多次调用isInterrupted()的返回结果一直返回true,当然抛InterruptedException之后由于中断状态被清除,isInterrupted()的返回结果为false。而执行了中断操作后,第一次调用interrupted()的结果为true,而且调用interrupted()也会清除中断状态,所以之后的中断状态一直为false,只有再次执行中断操作,才会返回true

在了解了两者的区别之后,针对不同的需求才能更好的选择使用哪个方法来监听中断状态

打中断标记

官方提供的中断方法是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
  • 对于系统提供的方法和自己打标记的方法又应该如何选择呢?实际上可以看到系统提供的interrupt()方法对于长时间挂起的线程,比如被Object.waitThread.sleep等方法阻塞住的线程也可以正常的停止。而如果是自己打标记的,如果线程被阻塞了,就无法正常响应达到停止的目的了。所以如果想要支持阻塞也能响应,就应选择系统提供的interrupt()方法,如果不需要支持,则更推荐使用自己打中断标记,从性能上来说会更加好一点。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容