美文网首页
多线程设计模式解读—Two-phase Termination,

多线程设计模式解读—Two-phase Termination,

作者: 九九派 | 来源:发表于2018-08-19 22:13 被阅读33次

    有时候,我们希望提前结束线程,但安全可靠地停止线程,并不是一件容易的事情,如果立即停止线程,会使共享的数据结构处于不一致的状态,如目前已经废弃使用的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

    相关文章

      网友评论

          本文标题:多线程设计模式解读—Two-phase Termination,

          本文链接:https://www.haomeiwen.com/subject/hvqdbftx.html