Correct stopping of multithreading based on Concurrency

Principle introduction

Use interrupt to notify instead of force.

It's easy to start a thread in JAVA, but when we stop a thread we don't stop it immediately. JAVA provides us with the method of interrupt, which simply adds a flag to the currently running thread to indicate that it can stop, but what exactly is this threadStopping at a time is not something we has the final say, it is up to the thread itself.

How to Stop Threads Correctly

1) First let's look at the general situation. If a thread is running half way and we want it to stop running, what should we do?

/**
 * @author Chen
 * @Description Stop a multithread normally
 * @create 2019-11-06 20:45
 */
public class StopThreadWithoutSleep  implements Runnable{
    /**
     * Print multiples of all 10,000, up to half the maximum integer
     */
    @Override
    public void run() {
        int num = 0;
        while (!Thread.currentThread().isInterrupted()&&num<=Integer.MAX_VALUE/2){
            if (num%10000 == 0){
                System.out.println(num+"Is a multiple of 10,000");
            }
            num++;

        }
        System.out.println("Task Run Ended");

    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThreadWithoutSleep());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

This lets the thread sleep for 1s and then calls interrupt() to tell the thread that it can stop, and then checks if it is running! Thread.currentThread().isInterrupted() executes the following program so that our multi-threaded program stops smoothly.

2) If the thread is blocked, it will affect how we stop the thread. Let's see how the thread stops when it is blocked:

/**
 * @author Chen
 * @Description Stop Thread with Block
 * @create 2019-11-06 20:58
 */
public class StopThreadWithSleep {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            try {
                int num = 0;
                while (num <= 300 && !Thread.currentThread().isInterrupted()) {
                    if (num % 100 == 0) {
                        System.out.println(num+"Is a multiple of 100");
                    }
                    num++;
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
    }
}

Run result:

This is an example of printing a multiple of 100 within 300, hibernating 1000ms after printing, and then stopping the thread after it runs 500ms. We know that the thread should be hibernating at this time. If we encounter a stop thread when calling this probable thread blocking method (e.g. sleep, wait), then we catch an exception, which can be done when the program enters the blocking processCan still respond to this interruption.

3) Let's move on to the next scenario: What if the thread gets blocked after each loop?

/**
 * @author Chen
 * @Description Every loop calls methods such as sleep or wait.
 * This does not require each iteration to determine if it has been interrupted
 * @create 2019-11-06 20:58
 */
public class StopThreadWithSleepEveryLoop {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            try {
                int num = 0;
                while (num <= 10000) {
                    if (num % 100 == 0) {
                        System.out.println(num+"Is a multiple of 100");
                    }
                    num++;
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

This example is printing 100 times less than 10,000. Threads sleep 10 ms after each loop and interrupt after 5,000 MS start. At this point, I can think about how quickly our program executes each loop, most of the time it hibernates 10 ms. When we call interrupt(), the thread can quickly respond to interrupts instead of interrupting.We need to pick up the break flag every time we start the cycle.

4) Note: Placing try catch inside while will cause interrupts to fail:

/**
 * @author Chen
 * @Description while Inside try/catch causes interrupts to fail
 * @create 2019-11-06 21:51
 */
public class CannotInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            while (num <=10000 && !Thread.currentThread().isInterrupted()){
                if (num%100== 0){
                    System.out.println(num+"Is a multiple of 100");
                }
                num++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

Run result:

This example is similar to the one above. It also prints 100 times less than 10000. Threads sleep 10 ms after each loop and interrupt after starting 5000ms. The difference is that try/catch statements are put into the loop this time. As we imagined above, the thread stops after printing out the exception information, but this is not the case..Here's a summary of the reasons:

Because we've already responded within the loop, printing out the exception information, the thread continues down to the next loop.We also added code to determine the flag bit in the next loop, which still doesn't work because JAVA designed the sleep with the idea that it will be cleared once the response is interrupted.

5) Best Practices Priority

/**
 * @author Chen
 * @Description Best practices: catch takes precedence over InterruptedException: throwing an exception in the method signature forces try/catch at run()
 * @create 2019-11-06 22:23
 */
public class RightWayStopThreadInProd1 implements  Runnable {
    @Override
    public void run() {
        while (true && !Thread.currentThread().isInterrupted()){
            System.out.println("go");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                //Save logs, stop programs
                System.out.println("Save Log");
                e.printStackTrace();
            }
        }
    }
    private void throwInMethod() throws InterruptedException {
        //If you try/catch instead of throwing it, the interrupt will be swallowed
        Thread.sleep(2000);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadInProd1());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

What we need to note here is that if we do a lot of business logic in the run method, other methods are called in the run method.Then if blocking occurs in other methods, and directly try/catch after blocking will make our run methods insensitive, unable to process and so on.The correct way to do this inside run() is to throw the exception up or reset the flag bit using the recovery break method described below.

6) Discontinuation of Best Practices Recovery

/**
 * @author Chen
 * @Description Best practice: Call Thread.currentThread().interrupt() in the catch clause to restore the set interrupt state.
 * In order to facilitate subsequent execution, it is still possible to check that an interrupt has just occurred
 * @create 2019-11-06 22:23
 */
public class RightWayStopThreadInProd2 implements  Runnable {
    @Override
    public void run() {
        while (true ){
            if (Thread.currentThread().isInterrupted()){
                System.out.println("End of Program Run");
                break;
            }
            System.out.println("go");
            throwInMethod();
        }
    }
    private void catchMethod() {
        //If you try/catch instead of throwing it, the interrupt will be swallowed
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadInProd2());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

This example resets the flag bit after try/catch in a method called within run(), that is, if catchMethod() detects an interrupt it throws an exception and responds to the interrupt, then restores the interrupt flag bit and handles the exception when it is caught: Thread.currentThread().interrupt() resets the flag bit so that normal interrupts can occur within the run methodThreaded.

6) Summary of methods that can respond to interruptions:

Object.wait()

Thraed.sleep()

Thread.join()

java.util.concurrent.BlockingQueue.take()/put()

java.util.concurrent.locks.Lock.lockInterruptibly()

java.util.concurrent.CountDownLatch.await()

java.util.concurrent.CyclicBarrier.await()

java.util.concurrent.Exchanger.exchange()

Related methods for java.nio.channels.InterruptibleChannel

Related methods of java.nio.channels.Selector

Wrong Stop Method

1) Deprecated stop, suspend, and resume methods

Stopping a thread with stop stops the thread immediately and does not guarantee that all tasks in a unit will be completed.If there is a transfer operation that requires a child thread to perform multiple transfers, stopping the child thread halfway can have serious consequences.

Thread suspend and resume, which are opposite operations, are suspended threads that must wait until the resume() operation to continue execution.Susnd() is not recommended because suspend() does not release any lock resources while causing the thread to pause.At this point, any other thread that wants to access a lock temporarily held by it will be implicated, preventing it from functioning properly and possibly causing a deadlock.

2) Use volatile to set boolean to set tag bits

Volatile can be simply understood as a keyword that solves the visibility problem of a variable between threads, and the value of the variable can be accessed by threads that add volatile.

Here's how this works:

The idea is to set an interrupt token yourself, check it at the beginning of the loop, and set the interrupt token to true when you want to stop the thread.

/**
 * @author Chen
 * @Description Example of stopping multithreads with Volatile
 * @create 2019-11-07 18:53
 */
public class UserVolatileInterrupt implements Runnable{
    //Set interrupt flag to false
    private volatile boolean canceled = false;

    @Override
    public void run() {
        int num = 10;
        try {
            while (num <= 100000&& !canceled){
                if (num%100 == 0){
                    System.out.println(num + "Is a multiple of 100.");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) throws InterruptedException {
        UserVolatileInterrupt v = new UserVolatileInterrupt();
        Thread thread = new Thread(v);
        thread.start();
        Thread.sleep(5000);
        v.canceled = true;
    }
}

Run result:

This may seem like a workable approach, but sometimes it doesn't stop properly: Here's another scenario

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * @author Chen
 * @Description Examples of stopping normally with volatile
 * @create 2019-11-07 19:07
 */
public class VolatileCannotStop {
    public static void main(String[] args) throws InterruptedException {
        //A blocking queue that enters a blocking state when an element is empty or full
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
        Producer producer = new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);


        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "Was consumed");
            Thread.sleep(100);
        }
        System.out.println("Consumers don't need more data.");
        //Once we don't need more data for consumption, we should stop producers too
        producer.canceled = true;
        System.out.println(producer.canceled);
    }
}

/**
 * Producer
 */
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 % 100 == 0) {
                    storage.put(num);
                    System.out.println(num + "Is a multiple of 10 and is put into the warehouse.");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("Producer End Run");
        }
    }
}

/**
 * Consumer
 */
class Consumer {
    BlockingQueue storage;

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

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

This is an example of a producer-consumer model where producers are responsible for continuously producing messages into storage s and consumers are randomly consuming data.Set the flag bit to true once the producer production data is not needed.This method seems feasible, but when it runs, it encounters the situation in the diagram and the program does not stop.

This is because producers produce fast, and consumers consume several messages and set the tag bit to true when the producer's queue is full, blocking storage.put(num) so that changes in the tag bit cannot be detected.

This is not the case if the interrupt method is used.

Stop thread-related method resolution

How to determine if it has been interrupted:

static boolean interrupted(): Gets the interrupt flag and resets it to the thread that is currently executing it, regardless of who invokes this method

boolean isInterrupted(): Get interrupt flag

Example:

/**
 * @author Chen
 * @Description
 * @create 2019-11-07 20:10
 */
public class RightWayInterrupted {

    public static void main(String[] args) throws InterruptedException {

        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                for (; ; ) {
                }
            }
        });

        // Start Thread
        threadOne.start();
        //Set interrupt flag
        threadOne.interrupt();
        //Get interrupt flag
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        //Get interrupt flag and reset
        System.out.println("isInterrupted: " + threadOne.interrupted());
        //Get break flag and straighten
        System.out.println("isInterrupted: " + Thread.interrupted());
        //Get interrupt flag
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        threadOne.join();
        System.out.println("Main thread is over.");
    }
}

Run result:

isInterrupted: true
isInterrupted: false
isInterrupted: false
isInterrupted: true

Note: threadOne.interrupted() and Thread.interrupted() actually work the same way

summary

How do I stop the thread?

Use interrupt instead of stop or volatile.Give the initiative to the interrupted thread.This ensures that the thread stops safely.

To achieve this effect, not only the interrupt method needs to be called, but also the requested party, the stopped party and the submethod callee need to cooperate with each other.As the requestor, Thread.interrupt() is called to send a signal for an interrupt request, and the stopped party must check the interrupt signal at the appropriate time and process it when an InterruptedException may be thrown.

If we are writing a submethod that needs to be invoked by the thread, it should be noted that the word method should throw an Exception before others can handle it or reset the interrupt flag bit after try/catch.

Tags: Java less

Posted on Thu, 07 Nov 2019 07:50:03 -0500 by pmt2k