Two Phase Termination:在一个线程t1中如何优雅终止线程t2?这里的“优雅”指的是给t2一个料理后事的机会。。
有一个监控线程,由于要去执行监控操作,因此它不断运行,每隔一秒钟执行一些监控操作,为了让他优雅退出,采用了中断的方式,比如在主线程中执行一个中断,若监控线程正在正常执行,中断这个监控线程后,它会设置中断标记为真,这时它不会进入catch块,等到下次循环判断时,发现这个中断标记为真,它可以做一些料理后事的工作,并退出这个监控循环。若主线程中断的监控线程正在sleep时,会进入catch块,因为sleep出现异常后,会清楚中断标记,因此需要获取到当前线程再次设置中断标记为真,这样下次循环时,它的中断标记为真,就可以继续执行料理后事,退出监控线程的循环。
这种方式的缺点是:需要时刻关注interruptException,一旦发生异常,需要重新设置中断标记,容易遗漏!若没有设置中断标记,下次判断时会导致这个if块不成立,不能正确执行料理后事,退出while(true)的循环。
之前使用interrupt()实现:
@Slf4j(topic = "c.TwoPharse1")
public class TwoPharse1 {
private Thread monitorThread;
public void start(){
monitorThread=new Thread(()->{
while(true){
Thread current=Thread.currentThread();
//是否被打断
if(current.isInterrupted()){
//被中断了
log.debug("料理后事");
break;
}
//没有被中断
try {
TimeUnit.SECONDS.sleep(1);
log.debug("执行监控记录");
} catch (InterruptedException e) {
//因为sleep出现异常后,会清楚中断标记
//需要设置中断标记
current.interrupt();
}
}
},"monitor");
monitorThread.start();
}
//在主线程中执行中断----停止监控线程
public void stop(){
monitorThread.interrupt();
}
}
现在使用volatitle实现:
在上述方式中添加boolean变量,当stop为真表示监控线程不应该在while(true)中循环了。
@Slf4j(topic = "c.TwoPharse1")
public class TwoPharse1 {
private Thread monitorThread;
private boolean stop=false;
public void start(){
monitorThread=new Thread(()->{
while(true){
Thread current=Thread.currentThread();
//是否被打断
if(stop){
//被中断了
log.debug("料理后事");
break;
}
//没有被中断
try {
TimeUnit.SECONDS.sleep(1);
log.debug("执行监控记录");
} catch (InterruptedException e) {
//因为sleep出现异常后,会清楚中断标记
//需要设置中断标记
current.interrupt();
}
}
},"monitor");
monitorThread.start();
}
//在主线程中执行中断----停止监控线程
public void stop(){
stop=true;
}
}
分析:一开始,中断标记为假,监控线程一直执行监控,当主线程调用中断方法,中断监控线程的执行后,设置中断标记为真,这时监控线程的while中的if块再判断时,发现中断标记为真,进入if块,料理后事,退出循环。
此时的问题时,因为monitorThread是一个线程,后来调用interrupt()方法的又是另外一个线程,两个线程对一个共享变量修改,让一个线程的修改对另一个线程可见,此时就需要使用volatitle,修饰共享变量,让一个线程对volatitle关键字修饰的变量的修改对另一个线程可见。
private volatitle boolean stop=false;
如果不加volatile,那可能其他线程将stop的值设置成真了,单monitorThread线程读到的值还是为假,那它就没办法正确退出了
还有一个小问题:监控线程正在sleep的过程中,某个线程执行了stop,将中断标记设置为真了,这时监控线程需要等到sleep结束下次进入循环判断时才会发现中断标记为真,退出循环,但是若监控线程的sleep的时间特别长,则需要很长时间才能到下次循环,若想某个线程执行stop方法设置中断标记为真后,监控线程尽快退出循环,则可以在stop()方法设置中断标记后,执行中断方法,中断中正在sleep的监控线程,让它尽快退出监控循环。
public void stop(){
stop=true;
monitorThread.interrupt();
}
测试方法:
TwoPharse1 twp=new TwoPharse1();
twp.start();
TimeUnit.SECONDS.sleep(5);
log.debug("停止监控");
twp.stop();
代码:
@Slf4j(topic = "c.TwoPharse1")
public class TwoPharse1 {
public static void main(String[] args) throws InterruptedException {
//测试方法
TwoPharse1 twp=new TwoPharse1();
twp.start();
TimeUnit.SECONDS.sleep(5);
log.debug("停止监控");
twp.stop();
}
private Thread monitorThread;
private boolean stop=false;
public void start(){
monitorThread=new Thread(()->{
while(true){
Thread current=Thread.currentThread();
//是否被打断
if(stop){
//被中断了
log.debug("料理后事");
break;
}
//没有被中断
try {
TimeUnit.SECONDS.sleep(1);
log.debug("执行监控记录");
} catch (InterruptedException e) {
}
}
},"monitor");
monitorThread.start();
}
//在主线程中执行中断----停止监控线程
public void stop(){
stop=true;
monitorThread.interrupt();
}
}
测试结果:
22:01:03.186 [monitor] DEBUG c.TwoPharse1 - 执行监控记录
22:01:04.193 [monitor] DEBUG c.TwoPharse1 - 执行监控记录
22:01:05.198 [monitor] DEBUG c.TwoPharse1 - 执行监控记录
22:01:06.202 [monitor] DEBUG c.TwoPharse1 - 执行监控记录
22:01:07.187 [main] DEBUG c.TwoPharse1 - 停止监控
22:01:07.187 [monitor] DEBUG c.TwoPharse1 - 料理后事