Sometimes we want to kill a thread early, but stopping a thread safely and reliably is not an easy thing to do. Stopping a thread immediately will leave the shared data structure in an inconsistent state. For example, the now-deprecated Thread stop method (which causes a Thread to terminate after a java.lang.ThreadDeath is thrown, even while synchronized). It is better to execute the Termination and then terminate the thread, namely two-phase Termination.

This mode has two characters:

  • Terminator is responsible for receiving the termination request, performing the termination process, and terminating itself after the completion of the process.
  • TerminationRequester: Originator of a termination request that sends a termination request to Terminator.

Example code for this schema is as follows: 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(a) {

        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(a) {
        System.out.println("do some clean work for the second phase,current counter "+counter);

    }

    public void close(a) {
        this.terminated = true;
        this.interrupt(); }}Copy the code

TerminationRequester:


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

        Thread.sleep(15_000L);
        // Active cleanupcounterIncrement.close(); }}Copy the code

This code shows that implementing the two-phase termination pattern requires the use of both the thread stop flag and the interrupt method


  public void close(a) {
        this.terminated = true;
        this.interrupt();
    }
Copy the code

Terminated as a thread stop signal and volatile variables are used to avoid the overhead of explicit locking and to ensure memory visibility. The thread run method checks the terminated attribute. If it is true, it stops the thread. But the thread may have called the blocking method and is in wait state. The thread may also be in the sleep() state and wait for the sleep time before executing the termination state, making the program less responsive. You can change the method to run as follows, and the thread stops significantly slower:


  public void close(a) {
        terminated = true;
  }
Copy the code
Simulate an example where either a client or a server can terminate a service

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(a) {
        this(DEFAULT_PORT);
    }

    public AppServer(int port) {
        this.port = port;
    }

    @Override
    public void run(a) {
        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(a) {
        System.out.println("dispose");
        this.clientHandlers.stream().forEach(ClientHandler::stop);
        this.executor.shutdown();
    }

    public void shutdown(a) throws IOException {
        this.start = false;
        this.interrupt();
        this.server.close(); }}Copy the code

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(a) {


        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) {
            // Automatically closes when running
            this.running = false;
        }finally {
            this.stop(); }}public void stop(a) {
        if(! running) {return;
        }
        this.running = false;
        try {
            this.socket.close();

        } catch (IOException e) {

        }
    }
}
Copy the code

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(); }}Copy the code

MAC Telnet simulates client input


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.
Copy the code

Server output:


Come from client >hello 
Come from client >I love you
dispose
Copy the code

Conclusion:

As you can see, when subclasses use the two-phase termination mode, all they need to do is implement the tasks they need to perform and update the number of current tasks. In some cases, the current number of tasks can also be left unupdated, such as when terminating without caring how many tasks are currently left to execute.