Java 并发系列十五 : 两阶段终止模式-如何优雅地终止线程?

前言

感谢王宝令老师的并发编程课程

今天咱们从技术的角度聊聊如何优雅的终止一个线程。线程执行完毕或者出现异常就会进入终止状态,这样看终止 一个线程很简单啊,不过我们今天谈的是“优雅地终止线程”, 不是自己终止自己,而是一个在T1 中,终止线程 T2; 这里所谓的“优雅”, 指的是给 T2 一个机会料理后事,而不是被一剑封喉。

Java 语言的 Thread 类中曾经提供了一个 stop() 方法, 用来终止线程。但是早已不建议使用,原因就是这个方法的作用就是一剑封喉的做法,被终止的线程没有料理后事的机会。

既然不建议使用,那如何优雅的终止线程呢?

如何理解两阶段终止模式

前辈们已经总结出一套成熟的方案,叫做两阶段终止模式。顾名思义就是就是将终止过程分为两阶段,第一个阶段就是线程T1向线程T2 发送终止指令,而第二阶段就是线程T2 响应终止指令。

img

那在 Java 语言里,终止指令是什么呢?这个要从 Java 线程的状态转换过程说起。

img

从这个图你会发现,Java 线程进入终止状态的前提是线程进入 RUNNABLE 状态, 而实际上线程可能处于休眠状态,也就是说,我们想要终止一个线程,首先要把线程的状态从休眠状态转换到RUNNABLE 状态。 这个如何做到?

这个要靠 Java Thread 类提供的 interrupt() 方法,它可以将休眠状态的线程转换到 RUNNABLE 状态。

线程转换到RUNNABLE 状态之后,我们然后将其终止 呢?RUNNABLE 切换到种子状态,优雅的方式就是让Java 线程自己执行完run() 方法,所以我们一般采用的方法是设置一个表示位,然后线程会在合适的时机检查这个标识位,如果发现这个终止条件,则自动退出run() 方法 。这个就是我们前面提到的 第二终止阶段:响应终止指令。

下面让我们来看一个实际中作种的案例。

用两阶段终止模式终止监控操作

实际工作中,有些监控系统需要动态的采集一些数据,一般都是监控系统发给需要监控的系统的监控代理,监控代理接到监控指令后,从监控目标收集数据,然后回传给监控系统,如下图。处于对性能的考虑(有些监控项对形态性能影响大,所以不能一直持续监控)动态采集功能一般都会有终止操作。

img

强烈建议你设置自己的线程终止标志位,例如在下面的代码中,使用 isTerminated 作为线程终止标志位,此时无论是否正确处理了线程的中断异常,都不会影响线程优雅地终止。


class Proxy {
  //线程终止标志位
  volatile boolean terminated = false;
  boolean started = false;
  //采集线程
  Thread rptThread;
  //启动采集功能
  synchronized void start(){
    //不允许同时启动多个采集线程
    if (started) {
      return;
    }
    started = true;
    terminated = false;
    rptThread = new Thread(()->{
      while (!terminated){
        //省略采集、回传实现
        report();
        //每隔两秒钟采集、回传一次数据
        try {
          Thread.sleep(2000);
        } catch (InterruptedException e){
          //重新设置线程中断状态
          Thread.currentThread().interrupt();
        }
      }
      //执行到此处说明线程马上终止
      started = false;
    });
    rptThread.start();
  }
  //终止采集功能
  synchronized void stop(){
    //设置中断标志位
    terminated = true;
    //中断线程rptThread
    rptThread.interrupt();
  }
}

如何优雅地终止线程池

Java 领域用的最多的还是线程池,而不是手动地创建线程。那我们该如何优雅地终止线程池呢? 线程池提供了两个方法:shutdown()和shutdownNow()。 了解它们的区别,就先需要了解线程池的实现原理。

我们曾经讲过 Java 线程池是生产者 - 消费者模式的一种实现, 提交给线程池的任务,首先是进入一个阻塞队列中,之后线程池中的线程从队列中取出任务执行。

shutdown() 方法是一种很保守的关闭线程池的方法。 线程池执行完shutdown() 后,就会拒绝接收新的任务,但是会等待正在执行的任务或者队列中的任务执行完毕再关闭线程池。

而 shutdownNow() 方法,相对就激进一些了,线程池执行 shutdownNow() 后,会拒绝接收新的任务,同时还会中断线程池中正在执行的任务,已经进入阻塞队列的任务也被剥夺了执行的机会,不过这些被剥夺执行机会的任务会作为 shutdownNow() 方法的返回值返回。因为 shutdownNow() 方法会中断正在执行的线程,所以提交到线程池的任务,如果需要优雅地结束,就需要正确地处理线程中断。

如果提交到线程池的任务不允许取消,那就不能使用 shutdownNow() 方法终止线程池。不过,如果提交到线程池的任务允许后续以补偿的方式重新执行,也是可以使用 shutdownNow() 方法终止线程池的。《Java 并发编程实战》这本书第 7 章《取消与关闭》的“shutdownNow 的局限性”一节中,提到一种将已提交但尚未开始执行的任务以及已经取消的正在执行的任务保存起来,以便后续重新执行的方案,你可以参考一下,方案很简答,这里就不详细介绍了。

其实分析完 shutdown() 和 shutdownNow() 方法你会发现,它们实质上使用的也是两阶段终止模式,只是终止指令的范围不同而已,前者只影响阻塞队列接收任务,后者范围扩大到线程池中所有的任务。

总结

两阶段提交模式是一种应用很广泛的并发设计模式,在 Java 语言中,使用两阶段终止模式来优雅的终止线程。需要两个关键点:一个是仅检查终止标识位是不够的,因为线程的状态可能处于休眠状态。另一个是仅检查线程的中断状态也是不够的,因为我们依赖的第三方类库很可能没有正确处理中断异常。

当使用Java 的线程池来管理线程的时候,需要依赖线程池提供的shutdown() 和 shutdownNow() 方法来终止线程。不过再使用的时候要注意他们的应用场景。尤其是使用shutdownNow() 的时候,一定要谨慎。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容