Playing with concurrency: how to stop threads gracefully?

Insert picture description here

Use stop method

Calling the stop method will directly terminate the running thread, which may prevent some cleanup work from being completed. And stop has been marked as an obsolete method and it is not recommended to use it.

The correct posture is to use a two-stage termination mode, that is, one thread sends a termination instruction, and the other thread receives the instruction, and decides when to stop.

Use flag

public class RunTask {
    
    

    private volatile boolean stopFlag;
    private Thread taskThread;

    public void start() {
    
    
        taskThread = new Thread(() -> {
    
    
            while (!stopFlag) {
    
    
                System.out.println("doSomething");
            }
        });
        taskThread.start();
    }

    public void stop() {
    
    
        stopFlag = true;
    }
}

Adding volatile to stopFlag is to ensure visibility. My example uses a while loop to continuously judge. If while is not used in the project, you can judge at the key node and then exit the run method.

Use the interrupt method

If there is blocking logic in our task, such as calling the Thread.sleep method, how to stop the thread?

Looking for the answer from the thread state transition diagram
Insert picture description here
From the diagram, you can see that if you want a thread to enter the termination state, the premise is that the thread is in the running state. When we want to terminate a thread, if the thread is in a blocking state at this time, how do we switch it to the running state?

We can transfer the blocked thread to the ready state by calling the Thread#interrupt method, enter the running state scheduled by the operating system, and then terminate.

What happens when the thread calls the interrupt method in the running state?

public class RunTaskCase1 {
    
    

    private Thread taskThread;

    public void start() {
    
    
        taskThread = new Thread(() -> {
    
    
            while (true) {
    
    
                System.out.println("doSomething");
            }
        });
        taskThread.start();
    }

    public void stop() {
    
    
        taskThread.interrupt();
    }
}

Call the start method and the stop method in turn, and found that the thread did not stop.

In fact, when the thread is running, the interrupt method just marks a stop in the current thread, and the logic of stopping needs to be implemented by ourselves.

The Thread class provides the following two methods to determine whether the thread is interrupted

  1. isInterrupted
  2. interrupted

Although these two methods can judge the status, there are subtle differences.

 @Test
 public void testInterrupt() throws InterruptedException {
    
    
     Thread thread = new Thread(() -> {
    
    
         while (true) {
    
    }
     });
     thread.start();
     TimeUnit.MICROSECONDS.sleep(100);
     thread.interrupt();
     // true
     System.out.println(thread.isInterrupted());
     // true
     System.out.println(thread.isInterrupted());
     // true
     System.out.println(thread.isInterrupted());
 }
 @Test
 public void testInterrupt2() {
    
    
     Thread.currentThread().interrupt();
     // true
     System.out.println(Thread.interrupted());
     // false
     System.out.println(Thread.interrupted());
     // false
     System.out.println(Thread.interrupted());
 }

The difference between isInterrupted and interrupted is as follows

Thread#isInterrupted: Test whether the thread is interrupted, and the status flag will not be changed after execution
Thread#interrupted: Test whether the thread is interrupted, and change the interrupt flag to false after execution

So at this time we don’t need to define our own state, just use the interrupt flag directly, the previous code can be changed as follows

public class RunTaskCase2 {
    
    

    private Thread taskThread;

    public void start() {
    
    
        taskThread = new Thread(() -> {
    
    
            while (!Thread.currentThread().isInterrupted()) {
    
    
                System.out.println("doSomething");
            }
        });
        taskThread.start();
    }

    public void stop() {
    
    
        taskThread.interrupt();
    }
}

When the thread is in a blocked state, calling the interrupt method will throw an InterruptedException and also terminate the execution of the thread

Note: When an exception occurs, the thread's interrupt flag will be changed from true to false.

So we have the following implementation.
When the thread is in the running state: exit with a self-defined flag bit.
When the thread is in the blocking state: exit by throwing an exception

public class RunTaskCase3 {
    
    

    private volatile boolean stopFlag;
    private Thread taskThread;

    public void start() {
    
    
        taskThread = new Thread(() -> {
    
    
            while (stopFlag) {
    
    
                try {
    
    
                    System.out.println("doSomething");
                    TimeUnit.MICROSECONDS.sleep(100);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        });
        taskThread.start();
    }

    public void stop() {
    
    
        stopFlag = true;
        taskThread.interrupt();
    }
}

Of course, you can always use the interrupt flag to exit. Note that the interrupt flag bit needs to be reset when an exception occurs .

public class RunTaskCase4 {
    
    

    private Thread taskThread;

    public void start() {
    
    
        taskThread = new Thread(() -> {
    
    
            while (!Thread.currentThread().isInterrupted()) {
    
    
                try {
    
    
                    System.out.println("doSomething");
                    TimeUnit.MICROSECONDS.sleep(100);
                } catch (InterruptedException e) {
    
    
                    // 重置中断标志位为true
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
            }
        });
        taskThread.start();
    }

    public void stop() {
    
    
        taskThread.interrupt();
    }
}

One last question for everyone? Which implementation method is better for RunTaskCase3 and RunTaskCase4?

Although the code of RunTaskCase4 looks more concise, it is not recommended to use RunTaskCase4, because if a third-party library is called in the run method, an InterruptedException occurs, but the interrupt flag bit is not reset, which will cause the thread to run forever, similarly to RunTaskCase2 It is also not recommended .

Reference blog

Guess you like

Origin blog.csdn.net/zzti_erlie/article/details/113854085