Java多线程 两阶段终止模式Two-Phase Termination Patter
作者:冬日毛毛雨 发布时间:2023-11-29 04:47:04
目录
1、两阶段终止模式介绍
2、Terminator代码演示
3、TerminationRequester
4、模拟客户端或者服务端都可能终止服务的例子
5、mac telnet模拟客户端输入
1、两阶段终止模式介绍
有时候,我们希望提前结束线程,但安全可靠地停止线程,并不是一件容易的事情,如果立即停止线程,会使共享的数据结构处于不一致的状态,如目前已经废弃使用的Thread类的stop方法(它会使线程在抛出java.lang.ThreadDeath
之后终止线程,即使是在执行synchronized
方法的时候)。更好的做法是执行完终止处理,再终止线程,即Two-phase Termination
,两阶段终止模式。
该模式有两个角色:
Terminator
,终止者,负责接收终止请求,执行终止处理,处理完成后再终止自己。TerminationRequester
:终止请求发出者,用来向Terminator
发出终止请求。
2、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();
}
}
3、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;
}
4、模拟客户端或者服务端都可能终止服务的例子
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();
}
}
5、mac telnet模拟客户端输入
bogon:~ kpioneer$ telnet localhost 12135
Trying ::1...
Connected to localhost.
Escape character is '^]'.
hello
echo hello
I love you
echo I love you
Connection closed by foreign host.
服务端输出:
Come from client >hello
Come from client >I love you
dispose
总结:
可以看到,在子类使用两阶段终止模式时,其只需要实现各自所需要执行的任务,并且更新当前任务的数量即可。在某些情况下,当前任务的数量也可以不进行更新,比如在进行终止时,不关心当前剩余多少任务需要执行。
来源:https://juejin.cn/post/7023614457683116069


猜你喜欢
- 本文实例讲述了Android DigitalClock组件用法。分享给大家供大家参考,具体如下:DigitalClock组件的使用很简单,先
- 创建SpringBoot项目,启动后,默认的访问路径即主机IP+默认端口号8080:http://localhost:8080/此时,我们就
- 如果不熟悉Java8新特性的小伙伴,初次看到函数式接口写出的代码可能会是一种懵逼的状态,我是谁,我在哪,我可能学了假的Java,(・∀・(・
- 先来了解一下什么是XMLType类型。XMLType是Oracle从9i开始特有的数据类型,是一个继承了Blob的强大存在,可以用来存储xm
- MyBatis的注解实现复杂映射开发实现复杂关系映射之前我们可以在映射文件中通过配置来实现,使用注解开发后,我们可以使用@Results注解
- 网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、
- Service层:public int addUser(UserDomian user){ int i = userMapper
- C#小程序飞行棋,程序效果图1、设计分析这个程序界面大致分为四部分:① 最上面游戏名字界面②信息提示区③游戏界面区④游戏操作提示区2、分区设
- 前面文章介绍了如何使用JAVA的反射机制来调用蓝牙的隐藏API,本文继续来练习JAVA的反射机制,探秘TelephonyManager在Fr
- 四个主要操作类:JsonConverter 、JsonHelper 、JsonSplit 、AjaxResult一、JsonConverte
- 之前一直使用各种报表工具,如RDLC、DevExpress套件的XtraReport报表,在之前一些随笔也有介绍,最近接触锐浪的Grid++
- java抠图片文字或签名运行原理第一步 遍历像素点BufferedImage image = ImageIO.read(new File(i
- 实验目的通过对进程调度算法的模拟,进一步理解进程的基本概念,加深对进程运行状态和进程调度过程、调度算法的理解。设备与环境硬件设备:PC机一台
- 前言Redis是一个开源的Key-Value数据缓存,和Memcached类似。Redis多种类型的value,包括string(字符串)、
- 在 Java 中,方法的重载(Overloading)和覆盖(Overriding)是两个重要的概念。它们都涉及到方法的定义与使用,但作用和
- 动态数据源在很多具体应用场景的时候,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库。又比如
- 我们在安装某个APP的时候,基本都会有一个引导页的提示,他们可以打广告,或者介绍新功能的加入和使用说明等。一般都支持滑动并且下面有几个点,显
- 本文实例为大家分享了Android实现简单手电筒功能的具体代码,供大家参考,具体内容如下XML:<?xml version="
- 最近在读刘增辉老师所著的《MyBatis从入门到精通》一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不
- Java未被捕获的异常在你学习在程序中处理异常之前,看一看如果你不处理它们会有什么情况发生是很有好处的。下面的小程序包括一个故意导致被零除错