Shocked! How can terminating a thread in this way cause service downtime?

Posted by snorky on Mon, 06 Apr 2020 17:34:00 +0200

Before we start, let's look at what's wrong with the following code?

public class ThreadStopExample {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                System.out.println("Subthread start execution");
                // Simulation business processing
                Thread.sleep(1000);
            } catch (Exception e) { }
            // Pseudocode: an important business method
            System.out.println("Important business methods of sub thread");
        });
        t1.start();
        // Let the child thread run a little business first
        Thread.sleep(100);
        // Terminate child thread
        t1.stop();
        // Wait for a period of time to ensure that the child thread "finishes executing"
        Thread.sleep(3000);
        System.out.println("Main thread execution completed");
    }
}

You may have found that the above code uses Thread.stop() to terminate the thread, which is not allowed in Java programs. What? Why can't you ask?

First of all, IDE will despise you. It will prevent you from using Thread.stop()!

What? You don't believe it. So look at this picture:

Well, then why can't you use it like this? Do you have to give me a perfunctory reason?

Problem 1: the integrity of the program is destroyed

In fact, for the code at the beginning of the article, its execution result is:

Subthread start execution

Main thread execution completed

We found a big problem. The most important pseudo code was not executed, as shown in the following figure:

It can be seen that after using stop() to terminate the thread, some of the remaining code of the thread will give up execution, which will cause a serious and hard to find big Bug. If the code that is not executed is the code that releases system resources, or the main logic processing code of this program. This destroys the integrity of the basic logic of the program, leading to unexpected problems, and it is also very secret, not easy to find and repair.

Some people say it's not easy. I'll add a finally?

This? There are all kinds of bar essence, especially this year.

OK, since this can't persuade you, let's look down.

Problem 2: breaking atomic logic

We know that synchronized in Java is an exclusive reentrant pessimistic lock. If we use it to decorate the code, it's OK to properly multithread, but if we encounter the stop() method, it's not necessarily. Let's look at the code directly.

public class ThreadStopExample {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        Thread t2 = new Thread(myThread);
        // Open thread
        t2.start();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(myThread);
            t.start();
        }
        // End thread
        t2.stop();
    }

    /**
     * Custom atomic test thread
     */
    static class MyThread implements Runnable {
        // Counter
        int num = 0;

        @Override
        public void run() {
            // Synchronous code block to ensure atomic operation
            synchronized (MyThread.class) {
                // Self increment
                num++;
                try {
                    // Thread sleep 0.1 seconds
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // Self decrement
                num--;
                System.out.println(Thread.currentThread().getName() + " | num=" + num);
            }
        }
    }
}

The execution results of the above procedures are as follows:

Thread-5 | num=1

Thread-4 | num=1

Thread-2 | num=1

Thread-1 | num=1

Thread-8 | num=1

Thread-6 | num=1

Thread-9 | num=1

Thread-3 | num=1

Thread-7 | num=1

Thread-10 | num=1

From the results, we can see that the code above is modified by synchronized + + and -- operations, and the final printed result num is not 0, but 1.

This is because the stop() method will release all locks in this thread, causing program execution disorder and destroying the atomic operation logic of the program.

The above problems lead to JDK abandoning the stop() method. Its source code is as follows:

/**
 * Forces the thread to stop executing.
 * <p>
 * If there is a security manager installed, its <code>checkAccess</code>
 * method is called with <code>this</code>
 * as its argument. This may result in a
 * <code>SecurityException</code> being raised (in the current thread).
 * <p>
 * If this thread is different from the current thread (that is, the current
 * thread is trying to stop a thread other than itself), the
 * security manager's <code>checkPermission</code> method (with a
 * <code>RuntimePermission("stopThread")</code> argument) is called in
 * addition.
 * Again, this may result in throwing a
 * <code>SecurityException</code> (in the current thread).
 * <p>
 * The thread represented by this thread is forced to stop whatever
 * it is doing abnormally and to throw a newly created
 * <code>ThreadDeath</code> object as an exception.
 * <p>
 * It is permitted to stop a thread that has not yet been started.
 * If the thread is eventually started, it immediately terminates.
 * <p>
 * An application should not normally try to catch
 * <code>ThreadDeath</code> unless it must do some extraordinary
 * cleanup operation (note that the throwing of
 * <code>ThreadDeath</code> causes <code>finally</code> clauses of
 * <code>try</code> statements to be executed before the thread
 * officially dies).  If a <code>catch</code> clause catches a
 * <code>ThreadDeath</code> object, it is important to rethrow the
 * object so that the thread actually dies.
 * <p>
 * The top-level error handler that reacts to otherwise uncaught
 * exceptions does not print out a message or otherwise notify the
 * application if the uncaught exception is an instance of
 * <code>ThreadDeath</code>.
 *
 * @exception  SecurityException  if the current thread cannot
 *               modify this thread.
 * @see        #interrupt()
 * @see        #checkAccess()
 * @see        #run()
 * @see        #start()
 * @see        ThreadDeath
 * @see        ThreadGroup#uncaughtException(Thread,Throwable)
 * @see        SecurityManager#checkAccess(Thread)
 * @see        SecurityManager#checkPermission
 * @deprecated This method is inherently unsafe.  Stopping a thread with
 *       Thread.stop causes it to unlock all of the monitors that it
 *       has locked (as a natural consequence of the unchecked
 *       <code>ThreadDeath</code> exception propagating up the stack).  If
 *       any of the objects previously protected by these monitors were in
 *       an inconsistent state, the damaged objects become visible to
 *       other threads, potentially resulting in arbitrary behavior.  Many
 *       uses of <code>stop</code> should be replaced by code that simply
 *       modifies some variable to indicate that the target thread should
 *       stop running.  The target thread should check this variable
 *       regularly, and return from its run method in an orderly fashion
 *       if the variable indicates that it is to stop running.  If the
 *       target thread waits for long periods (on a condition variable,
 *       for example), the <code>interrupt</code> method should be used to
 *       interrupt the wait.
 *       For more information, see
 *       <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why
 *       are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
 */
@Deprecated
public final void stop() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        checkAccess();
        if (this != Thread.currentThread()) {
            security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
        }
    }
    // A zero status value corresponds to "NEW", it can't change to
    // not-NEW because we hold the lock.
    if (threadStatus != 0) {
        resume(); // Wake up thread if it was suspended; no-op otherwise
    }

    // The VM can handle all thread states
    stop0(new ThreadDeath());
}

It can be seen that the stop() method is modified by the @ Deprecated annotation, and the code modified by this annotation is represented as an obsolete method, which is not recommended. As can be seen from the remarks of stop (), stop () is not officially recommended as an unsafe method.

Thread terminated correctly

How to terminate a thread? Here are 2 correct methods:

  1. Set the exit ID to exit the thread;
  2. Use the interrupt() method to terminate the thread.

1. User defined exit ID

We can customize a boolean variable to identify whether to exit the thread. The implementation code is as follows:

// Custom exit ID exit thread
static class FlagThread extends Thread {
    public volatile boolean exit = false;

    public void run() {
        while (!exit) {
            // Perform normal business logic
        }
    }
}

We can see that we use the keyword volatile to decorate the thread, so as to ensure the safety of multi-threaded execution. When we need to let the thread exit, we only need to assign the variable exit to true.

2.interrupt terminate thread

When we use the interrupt() method, the execution results of the above two examples are normal. The execution code is as follows:

public class ThreadStopExample {
    public static void main(String[] args) throws InterruptedException {
        // Problem 1: the integrity of the program is destroyed
        Thread t1 = new Thread(() -> {
            try {
                System.out.println("Subthread start execution");
                // Simulation business processing
                Thread.sleep(1000);
            } catch (Exception e) { }
            // Pseudo code: important business method
            System.out.println("Important business methods of sub thread");
        });
        t1.start();
        // Let the child thread run a little business first
        Thread.sleep(100);
        // Terminate child thread
        t1.interrupt();
        // Wait for a period of time to ensure that the child thread "finishes executing"
        Thread.sleep(3000);
        System.out.println("Main thread execution completed");

        // Problem 2: breaking atomic logic
        MyThread myThread = new MyThread();
        Thread t2 = new Thread(myThread);
        // Open thread
        t2.start();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(myThread);
            t.start();
        }
        // End thread
        t2.interrupt();
    }

    /**
     * Custom atomic test thread
     */
    static class MyThread implements Runnable {
        // Counter
        int num = 0;

        @Override
        public void run() {
            // Synchronous code block to ensure atomic operation
            synchronized (MyThread.class) {
                // Self increment
                num++;
                try {
                    // Thread sleep 0.1 seconds
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    System.out.println(e.getMessage());
                }
                // Self decrement
                num--;
                System.out.println(Thread.currentThread().getName() + " | num=" + num);
            }
        }
    }
}

The execution results of the above procedures are as follows:

Subthread start execution

Important business methods of sub thread

Main thread execution completed

sleep interrupted

Thread-1 | num=0

Thread-9 | num=0

Thread-10 | num=0

Thread-7 | num=0

Thread-6 | num=0

Thread-5 | num=0

Thread-4 | num=0

Thread-2 | num=0

Thread-3 | num=0

Thread-11 | num=0

Thread-8 | num=0

It can be seen that the above execution is in line with our expectation, which is the right way to terminate the thread.

summary

In this article, we talked about three ways to terminate a thread: the way to customize the exit ID, the way to use stop() or the way to interrupt(). The stop () method will lead to the problem that the integrity and atomicity of the program will be destroyed, and this method is identified as the expired method by JDK, which is not recommended, and the interrupt () method is undoubtedly the most suitable way for us to terminate the thread.

For more highlights, please pay attention to "Java Chinese community"

Topics: Programming Java JDK