The way of thread interruption and the inappropriate scenario of vilotile

principle

Usually, instead of manually stopping a thread, we allow the thread to run to the end and then let it stop naturally. However, there are still many special situations that require us to stop the thread in advance, such as the user suddenly closes the program, or the program restarts due to an error.

In this case, the thread that is about to stop is still valuable in many business scenarios. Especially when we want to write a program that is robust and can safely deal with various scenarios, it is particularly important to stop the thread correctly. However, Java does not provide the ability to stop threads directly and safely.

Why not force a stop? It's notification and collaboration

The most correct way to stop a thread is to use interrupt.
interrupt function: the function of notifying the stopped thread
Why is it just notification:
For the stopped thread, it has complete autonomy. It can choose to stop immediately, after a period of time, or not at all.

Why doesn't Java provide the ability to force threads to stop

  • Management threads that want programs to notify and cooperate with each other

Because if you don't know what the other party is doing, hastily forcibly stopping the thread may cause some safety problems. In order to avoid causing problems, you need to give the other party some time to sort out the finishing work. For example, when a thread is writing a file and receives a termination signal, it needs to judge whether to stop immediately or stop after successfully writing the whole file according to its own business. If you choose to stop immediately, the data may be incomplete. Neither the initiator nor the receiver of the interrupt command wants the data to have problems.

How to stop a thread with interrupt

while (!Thread.currentThread().isInterrupted() && more work to do) {
    do more work
}

How to use code to realize the logic of stopping threads.

Once we call interrupt() of a thread, the interrupt flag bit of the thread will be set to true. Each thread has such a flag bit. When the thread executes, it should be checked regularly. If the flag bit is set to true, it indicates that a program wants to terminate the thread. Back to the source code, you can see that in the while loop body judgment statement, first pass thread currentThread(). Isinterrupt () determines whether the thread has been interrupted, and then checks whether there is still work to be done&& Logic indicates that the following work will be performed only when the two judgment conditions are met at the same time.

public class StopThread implements Runnable {
    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println("count=" + count++);
        }
    }

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new StopThread());
        thread.start();
        Thread.sleep(30);
        thread.interrupt();
    }
}



In the run() method of StopThread class, first judge whether the thread is interrupted, and then judge whether the count value is less than 1000. The work content of this thread is very simple. It is to print numbers from 0 to 999. Add 1 to the count value of each number. You can see that the thread will check whether it is interrupted before each cycle starts. Next, the thread will be started in the main function, and then the thread will be interrupted immediately after sleeping for 5 milliseconds. The thread will detect the interrupt signal, so it will stop before 1000 numbers are printed. This is the case of correctly stopping the thread through interrupt.

Can you feel the interruption during sleep

public class StopRunningThread {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(() -> {
            int num = 1;
            try {
                while (!Thread.currentThread().isInterrupted() && num <= 1000) {
                    System.out.println(num++);
                    Thread.sleep(100000);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        thread.start();
        Thread.sleep(100);
        thread.interrupt();
    }
}


The results show that if sleep, wait and other methods can let the thread enter the blocking method to make the thread sleep, and the thread in sleep is interrupted, the thread can feel the interrupt signal, throw an InterruptedException exception, clear the interrupt signal and set the interrupt flag bit to false. In this way, there is no need to worry that the thread will not feel the interrupt during long-term sleep, because even if the thread is still sleeping, it can still respond to the interrupt notification and throw an exception.

Two processing methods of thread interrupt

  • Method throws InterruptedException
  • Try / catch interrupt again
private void reInterrupt() {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        e.printStackTrace();
    }
}

Why is the stop method of marking bits with volatile wrong

Wrong stop method

  1. stop(): stop the thread directly. The thread has no time to save its logic before stopping, and data integrity problems will occur
  2. Combination of suspend() and resume(): the thread starts to sleep without releasing the lock. It is easy to have deadlock problems when holding the lock, and the thread will not release before resume()
  3. volatile flag bit
  • Correct stop method
public class VolatileCanStop {
    private static boolean cancel = false;
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(() -> {
            int num = 0;
            while (!cancel && num < 10000) {
                if (num % 10 == 0) {
                    System.out.println("10 Multiple of:" + num);
                }
                num++;
                try {
                    Thread.sleep(1);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        Thread.sleep(100);
        cancel = true;
    }
}


In the above code, the Runnable anonymous thread is used, and then the while loop is carried out in run(). Two layers of judgment are carried out in the loop body. First, judge the value of the cancelled variable. The cancelled variable is a Boolean value modified by volatile with an initial value of false. When the value becomes true, while jumps out of the loop, The second judgment condition of while is that the num value is less than 1000000 (one million). In the while loop body, it will be printed as long as it is a multiple of 10, and then num + +.

  • Error stop method
  1. First, a Producer is declared to stop the thread through the Boolean value cancelled with the initial value of false of volatile tag. In the run() method, the judgment statement of while is whether num is less than 100000 and whether {cancelled is marked. In the while loop body, if num is a multiple of 50, it will be put into the storage warehouse. Storage is the memory for communication between producers and consumers. When num is greater than 100000 or is notified to stop, it will jump out of the while loop and execute the finally statement block to tell everyone "the Producer ends running".
class Producer implements Runnable {

    public volatile boolean canceled = false;
    BlockingQueue storage;

    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 50 == 0) {
                    storage.put(num);
                    System.out.println(num + "Is a multiple of 50,Put it in the warehouse.");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("The producer ends the operation");
        }
    }
}
  1. For the Consumer, it shares the same warehouse storage with the producer, and determines whether more numbers need to be used in the method through the needmorenum() method. Just now, the producer has produced some multiples of 50 for consumers to use. The judgment condition of whether consumers continue to use numbers is to generate a random number and compare it with 0.97, Numbers greater than 0.97 will no longer be used.
class Consumer {
    BlockingQueue storage;

    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }

    public boolean needMoreNums() {
        if (Math.random() > 0.97) {
            return false;
        }
        return true;
    }
}

  1. Main method. Let's look at the main function. First, create the BlockingQueue storage shared by producers / consumers. The warehouse capacity is 8. After establishing the producer and putting the producer into the thread, start the thread. After starting, sleep for 500 milliseconds. The sleep time ensures that the producer has enough time to fill the warehouse, After the warehouse reaches the capacity, it will not continue to plug in. At this time, the producer will block. After 500 milliseconds, the consumer will also be created and judge whether more numbers need to be used, and then sleep for 100 milliseconds after each consumption. Such business logic may appear in actual production.

When the consumer no longer needs data, it will set the cancelled flag bit to true. In theory, the producer will jump out of the while loop and print out "the producer runs over".

public class VolatileCannotStop {

    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue storage = new ArrayBlockingQueue(8);

        Producer producer = new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(500);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "Consumed");
            Thread.sleep(100);
        }
        System.out.println("Consumers don't need more data.");

        //Once consumption doesn't need more data, we should let producers stop, but the actual situation can't stop
        producer.canceled = true;
        System.out.println(producer.canceled);
    }
}

However, the result is not what we think. Although canceled has been set to true, the producer still does not stop, because in this case, the producer is executing storage When put (Num) is blocked, there is no way to enter the next cycle to judge the value of cancelled before it is woken up. Therefore, in this case, there is no way to stop the producer with volatile. On the contrary, if the interrupt statement is used to interrupt, even if the producer is in the blocking state, it can still feel the interrupt signal and respond.

summary

  1. Interrupt threads use interrupt()
  2. Method declares InterruptException or catch after thread currentThread(). interrupted();
  3. Do not use stop(), suspend(), resume() to combine methods that are marked obsolete
  4. Try to avoid using volatile to stop threads

Keywords: Java

Added by Byron on Sun, 06 Mar 2022 04:59:25 +0200