多线程设计模式解读3—Two-phase Termination(两阶段终止)模式

有时候,我们希望提前结束线程,但安全可靠地停止线程,并不是一件容易的事情,如果立即停止线程,会使共享的数据结构处于不一致的状态,如目前已经废弃使用的Thread类的stop方法(它会使线程在抛出java.lang.ThreadDeath之后终止线程,即使是在执行synchronized方法的时候)。更好的做法是执行完终止处理,再终止线程,即Two-phase Termination,两阶段终止模式。

该模式有两个角色:

Terminator,终止者,负责接收终止请求,执行终止处理,处理完成后再终止自己。

TerminationRequester:终止请求发出者,用来向Terminator发出终止请求。

该模式示例代码如下:

Terminator:

public class TerminateTestThread extends Thread {
    // 计算值
    private long num = 0;

    // 是否关闭标志
    private volatile boolean isShutdown = false;

    // 终止请求
    public void terminate() {
        isShutdown = true;
        interrupt();
    }

    // 检查关闭状态
    public boolean isShutdown() {
        return isShutdown;
    }

    @Override
    public void run() {
        try {
            while (!isShutdown()) {
                doWork();
            }
        } catch (InterruptedException e) {
        } finally {
            doShutdown();
        }
    }

    // 操作
    private void doWork() throws InterruptedException {
        num++;
        System.out.println("num:  = " + num);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
    }

    // 终止处理
    private void doShutdown() {
        System.out.println("doShutdown: Save result");
        System.out.println("doShutdown result: num = " + num);
        System.out.println("doShutdown: Save END");
    }
}

TerminationRequester:

public class TerminationRequesterMain {
    public static void main(String[] args) {
        try {
            // 启动线程
            TerminateTestThread t = new TerminateTestThread();
            t.start();

            //等待一段时间
            Thread.sleep(1000);

            // 发送线程的终止请求
            t.terminate();

            // 等待线程终止
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这段代码可以看出实现两阶段终止模式必须注意的是:

使用线程停止标志和interrupt方法,两者缺一不可

public void terminate() {
        isShutdown = true;
        interrupt();
    }

这里使用了isShutdown作为线程停止标志,变量采用volatile修饰,避免了使用显式锁的开销,又保证了内存可见性。线程run方法会检查isShutdown属性,如果属性为true,就停止线程,但线程可能调用了阻塞方法,处于wait状态,任务也就可能永远不会检查isShutdown标志;线程也有可能处于sleep()状态,等sleep时间过后再执行终止状态,程序的响应性就下降了。你可以把方法改成如下运行,线程停止明显变慢了许多:

public void terminate() {
        isShutdown = true;
  }

因此,需要调用interrupt()方法,发出中断线程的请求。那么,但为什么不直接在run方法中使用isInterrupted方法检查线程是否处于中断状态呢,如下面代码:

@Override
    public void run() {
        try {
            while (!isInterrupted()) {
                doWork();
            }
        } catch (InterruptedException e) {
        } finally {
            doShutdown();
        }
    }

运行后发现线程始终没有停止,这是因为在doWork方法内部,sleep方法抛出InterruptedException异常后中断状态被清除,捕获时也没有作出任何处理,需要Thread.currentThread().interrupt()保留线程中断状态,我们把方法稍微改动即可:

// 操作
    private void doWork() throws InterruptedException {
        num++;
        System.out.println("num:  = " + num);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

因此,同时使用线程停止标志和interrupt方法,其实是给线程停止操作上了双重保险,开发人员或许会忽略InterruptedExceptio异常,或许线程处于wait或者长时间的sleep的状态,这些情况都要提前考虑好。

以上是一个简单的Two-phase Termination(两阶段终止模式)范例,在复杂实现中,我们可能还要考虑其他方面的内容,如如何停止处于生产者-消费者模式中的线程,停止顺序是怎样的,在停止时如何处理队列中的待处理任务;如果有多个可停止线程,那么线程停止标志怎样实现共享,减少锁的使用。我们需要一套可复用的解决方案,来综合考虑这些问题。

本文分享自微信公众号 - java达人(drjava)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-08-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

自由程序员的3个开发技巧

我们有三个系列的小技巧要分享:与你的客户沟通,保护你的声誉以及解决常见的自由职业问题。下面让我们开始吧!

8710
来自专栏跟着阿笨一起玩NET

YYYY-mm-dd HH:MM:SS

87520
来自专栏java一日一条

作为一个程序员我最大的遗憾

20多年前我处在了人生的一个十字路口。随着我们用5年时间为出版社(他们希望进入新兴的互联网空间)打造的Deltagraph的终结,我的第二家公司逐渐走向没落。那...

8510
来自专栏黄Java的地盘

《麦肯锡 问题分析与解决技巧》——阅读笔记(共16章)

《麦肯锡 问题分析与解决技巧》是一本关于问题分析与如何解决问题的书籍,在阅读过程中收获不少,因此留下读书笔记方便其他人来进行学习。

79110
来自专栏跟着阿笨一起玩NET

NULL不能和任何字段比较和运算

14410
来自专栏跟着阿笨一起玩NET

Image.FromFile 锁文件的解决办法

7310
来自专栏跟着阿笨一起玩NET

不允许对64位应用程序进行修改”的解决方法

在64位系统中使用VS对程序(32位的)进行调试,出现“不允许对64位应用程序进行修改”的提示,如下图所示:

13310
来自专栏跟着阿笨一起玩NET

无法将类型“System.Collections.Generic.IEnumerable<EmailSystem.Model.TemplateInfo>”隐式转换为“System.Collection

38210
来自专栏java一日一条

熬夜并不值得程序员炫耀

放弃睡眠就像是高利贷借款。没错,看上去你是得到了额外的时间,但你想得太乐观了,你知道代价是什么吗?放高利贷的会回来讨债,到时如果你不能支付的话,他就会夺走你的创...

7710
来自专栏雪地二货笔记库

vue学习笔记9-监听属性-watch

这样就可以实现绑定 而且他后面也有很多功能,比如.lazy,惰性更新,只有焦点离开才会更新

9210

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励