Java Concurrent Programming 02: Stop Threads Gracefully

Get into the habit of writing together! This is the 4th day of my participation in the "Nuggets Daily New Plan·April Update Challenge", click to view the details of the event .

Introduction

We can start the thread by calling the start()method , but stopping the thread is a bit more complicated.

Typically, instead of manually stopping a thread, we allow the thread to run until the entire process ends, and then let it stop naturally. However, for some special cases, the thread needs to be stopped in advance, such as the user suddenly closes the program, the program runs in error, etc. Threads that are about to be stopped at this time are still valuable in many business scenarios, but Javathey do not provide the ability to directly and safely stop threads that are easy to use.

JavaFor , the most correct way to stop a thread is to use interrupt(), but it only serves as a notification to the thread being stopped. For the stopped thread, it has full autonomy, and can choose to stop immediately, after a period of time, or not at all.

Why Javanot provide the ability to force stop a thread?

  • JavaIt is hoped that programs can notify each other and manage threads cooperatively
  • Because if you don't know what the other party is doing, rushing to stop the thread may cause some security problems
  • For example, when a thread is writing a file, when it receives a termination signal, it needs to decide according to its own business whether to choose to stop immediately, or to stop after writing the entire file successfully, and if it chooses to stop immediately, it may cause data confusion. Neither the originator of the interrupt command nor the receiver want data problems

The wrong way to stop a thread

The following three ways of stopping a thread are marked as obsolete @Deprecated:

  • stop(): Stop the thread from running. stop()The thread will be stopped directly, although it will release the lock, but it will cause the task to stop abruptly, not giving the thread enough time to process the finishing work, and data integrity and other problems may occur.
  • suspend()/ resume(): Suspend thread execution / Resume thread execution. suspend()After the thread calls , the lock is not released. resume()The lock will not be released until the thread is called. This can lead to deadlock problems.
  • With the help of volatilemarker bits

Why is it wrong to stop a thread with the help of the volatileflag bit?

In some cases, the volatileflag bit can stop the thread gracefully, as follows:

public class VolatileStopThread implements Runnable {
    private volatile boolean canceled = false;
	
    @Override
    public void run() {
        int num = 0;
        try {
            while (!canceled && num <= 1000000) {
                if (num % 10 == 0) {
                    System.out.println(num + "是10的倍数");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileStopThread r = new VolatileStopThread();
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(3000);
        r.canceled = true;
	}
}
复制代码

However, if the stopped thread is blocked in the middle of execution (such as adding an element to a full blocking queue), then it will not be able to detect that the volatileflag bit has been set false, and will not be able to break out of the loop. So the thread cannot be stopped with the help of the volatileflag bit.

interrupt() method and interrupt flag bit

The interrupt()method can stop the thread, which is divided into two cases here:

  • Calling a interrupt()method sets the interrupt flag bit.
  • Calling the interrupt()method , such as the thread that calls the sleep(), wait(), join()method, will throw InterruptedExceptionan exception , and at the same time clear the interrupt flag, that is, set it to false.

What are the ways to access interrupt flag bits?

  • isInterrupted(): This method does not affect break flags.
  • Static method Thread.interrupted(): After returning the break flag, it will be set to false. That is, this method can be used to clear the interrupt flag bit.

Stop the thread correctly

Method 1 : Declare throwing an exception in the method signature run(), and use in the method to try/catchcatch the exception.

  • throws InterruptedException 标记方法,不捕获异常,以便该异常可以传递到顶层的 run() 方法
  • 由于 run() 方法无法抛出受检异常,所以只能使用 try/catch 捕获异常,这样的好处在于,顶层方法必须处理该异常

方式二:在 catch 块中再次中断线程。

  • catch 块中调用 Thread.currentThread().interrupt() 函数
  • 因为如果线程在休眠期间被中断,那么会自动清除中断信号
  • 这时如果手动添加中断信号,中断信号仍然可以被捕捉到

如果盲目地忽略中断请求,也就是屏蔽中断信号,可能会导致线程无法被正确停止。

两阶段终止模式

上面的方式二其实就是两阶段终止模式

两阶段终止模式 Two-Phase Termination Pattern 是一种多线程设计模式,用于在线程 t1 中优雅地停止另一个线程 t2,“优雅”指的是给被停止的线程做收尾工作的机会。

两阶段终止分为两种中断情况:

  • 运行时中断,打断标记已经为 true,只需要检测即可
  • 睡眠时中断,需要捕捉异常,以及重新设置打断标记为 true

团子注:使用 interrupt() 实现两阶段终止。关键点在于对于 sleepjoin 或者 wait 等处于阻塞状态的线程,interrupt() 只会抛出 InterruptedException,而不会设置打断标记,所以只要在异常处理内容中手动再次 interrupt() 重新设置打断标记即可。

流程图如下:

202204041124 Java Concurrent Programming 02: Stop Threads Gracefully 00.png

示例代码如下:

class TPTInterrupt {
	private Thread thread;
	
	public void start(){
		thread = new Thread(() -> {
			while(true) {
				Thread current = Thread.currentThread();
				if(current.isInterrupted()) {
					log.debug("料理后事");
					break;
				}
				try {
					Thread.sleep(1000);
					log.debug("将结果保存");
				} catch (InterruptedException e) {
					current.interrupt();
				}
				// 执行监控操作 
			}
		}, "监控线程");
		thread.start();
	}
	
	public void stop() {
		thread.interrupt();
	}
}
复制代码

调用:

TPTInterrupt t = new TPTInterrupt();
t.start();
Thread.sleep(3500);
log.debug("stop");
t.stop()
复制代码

结果:

11:49:42.915 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:43.919 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:44.919 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:45.413 c.TestTwoPhaseTermination [main] - stop 
11:49:45.413 c.TwoPhaseTermination [监控线程] - 料理后事
复制代码

参考资料

  • Pull Hook: JavaConcurrent Programming 78Lecture Part 2Lecture
  • [[202112052113 Java multithreading: interrupt method]]
  • [[202112052155 Java Multithreading Design Patterns: Two-Phase Termination]]
  • [[202112052217 Java Multithreading: Deprecated Obsolete Method]]

Guess you like

Origin juejin.im/post/7083519665414078471