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

2018-08-19  本文已影响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
上一篇下一篇

猜你喜欢

热点阅读