为了账号安全,请及时绑定邮箱和手机立即绑定

Java多线程19 两阶段终止模式(Two-Phase Termination Patter)

标签:
Java

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

该模式有两个角色:

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

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

该模式示例代码如下:
Terminator:

public class CounterIncrement extends Thread {    private volatile boolean terminated = false;    private int counter = 0;    private Random random = new Random(System.currentTimeMillis());    @Override
    public void run() {        try {            while (!terminated) {
                System.out.println(Thread.currentThread().getName()+" "+counter++);
                Thread.sleep(random.nextInt(1000));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {            this.clean();
        }
    }    private void clean() {
        System.out.println("do some clean work for the second phase,current counter "+counter);

    }    public void close() {        this.terminated = true;        this.interrupt();
    }
}

TerminationRequester:

public class CounterTest {    public static void main(String[] args) throws InterruptedException {
        CounterIncrement counterIncrement = new CounterIncrement();
        counterIncrement.start();

        Thread.sleep(15_000L);        //主动清理
        counterIncrement.close();
    }
}

这段代码可以看出实现两阶段终止模式必须注意的是:
使用线程停止标志和interrupt方法,两者缺一不可

  public void close() {        this.terminated = true;        this.interrupt();
    }

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

  public void close() {
        terminated = true;
  }
模拟客户端或者服务端都可能终止服务的例子
public class AppServer extends Thread {    private static final int DEFAULT_PORT = 12722;    private final static ExecutorService executor = Executors.newFixedThreadPool(10);    private int port;    private volatile boolean start = true;    private List<ClientHandler> clientHandlers = new ArrayList<>();    private ServerSocket server;    public AppServer() {        this(DEFAULT_PORT);
    }    public AppServer(int port) {        this.port = port;
    }    @Override
    public void run() {        try {
            server = new ServerSocket(port);            while (start) {
                Socket client = server.accept();
                ClientHandler clientHandler = new ClientHandler(client);
                executor.submit(clientHandler);                this.clientHandlers.add(clientHandler);
            }

        } catch (IOException e) {            //throw new RuntimeException();
        } finally {            this.dispose();
        }
    }    public void dispose() {
        System.out.println("dispose");        this.clientHandlers.stream().forEach(ClientHandler::stop);        this.executor.shutdown();
    }    public void shutdown() throws IOException {        this.start = false;        this.interrupt();        this.server.close();
    }
}
public class ClientHandler implements Runnable {    private final Socket socket;    private volatile boolean running = true;    public ClientHandler(Socket socket) {        this.socket = socket;
    }    @Override
    public void run() {        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream();
             BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
             PrintWriter printWriter = new PrintWriter(outputStream)) {            while (running) {
                String message = br.readLine();                if (message == null) {                    break;
                }
                System.out.println("Come from client >" + message);
                printWriter.write("echo " + message+"\n");
                printWriter.flush();
            }
        } catch (IOException e) {            //自动关闭的时候 将running
            this.running = false;
        }finally {            this.stop();
        }

    }    public void stop() {        if (!running) {            return;
        }        this.running = false;        try {            this.socket.close();

        } catch (IOException e) {

        }
    }
}
public class AppServerClient {    public static void main(String[] args) throws InterruptedException, IOException {
        AppServer server = new AppServer(12135);
        server.start();

        Thread.sleep(20_000L);
        server.shutdown();
    }
}

mac telnet模拟客户端输入

bogon:~ kpioneer$ telnet localhost 12135
Trying ::1...
Connected to localhost.
Escape character is '^]'.
hello 
echo hello 
I love youecho I love you
Connection closed by foreign host.

服务端输出:

Come from client >hello 
Come from client >I love you
dispose

总结:

可以看到,在子类使用两阶段终止模式时,其只需要实现各自所需要执行的任务,并且更新当前任务的数量即可。在某些情况下,当前任务的数量也可以不进行更新,比如在进行终止时,不关心当前剩余多少任务需要执行。



作者:香沙小熊
链接:https://www.jianshu.com/p/8cfa2dabbbce


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
205
获赞与收藏
1007

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消