Thread interrupt and LockSupport

Keywords: Java JDK

This article will introduce the following points, readers and friends can also first think about the relevant issues:

  1. What does the thread interrupt method mean, is it a thread interrupt?Can the current thread continue execution?
  2. There are several ways to tell if a thread is interrupted, and what is the difference between them?
  3. What is the difference between park/unpark and wait/notify for LockSupport?
  4. How does the sleep method respond to interruptions?
  5. How does the park method respond to interrupts?

Thread interrupt related methods

There are three interrupt-related methods in a thread, described below:

1) interrupt

We usually say this method is used to interrupt threads, so what should we understand about this interrupt?Does that mean breaking the thread currently executing and not letting it continue executing?

In fact, it is not.Here, the interrupt is simply an interrupt identifier (set to true) for the thread, and the thread continues executing down.How the thread stops is up to us.This will be illustrated in code later.

2) isInterrupted

Determines the interrupt state of the current thread, that is, whether the thread's interrupt identifier is true or false.Note that this method has no effect on the thread's original interrupt state.

3) interrupted

It also determines the interrupt state of the thread.However, it should be noted that this method is very different from isInterrupted.Let's look at their source code:

public boolean isInterrupted() {  
    return isInterrupted(false);  
}

public static boolean interrupted() {  
    return currentThread().isInterrupted(true);  
}
//Call the same method, but pass different parameters
private native boolean isInterrupted(boolean ClearInterrupted);

First, the isInterrupted method is the method of the thread object, and interrupted is the static method of the Thread class.

Second, they all call the same local method, isInterrupted, except for the value passed in, which indicates whether or not the thread's interrupt state should be cleared (that is, whatever the previous value of the interrupt state was, it would eventually be set to false).

Therefore, the interrupted static method clears the interrupt state of the original thread, whereas isInterrupted does not.So, if you call the interrupted method twice, you will always return false the second time unless you are interrupted again in the middle.

The following demonstrates that the interrupt method only sets an interrupt state, not interrupts the current thread:

public class TestFlag {
    static volatile boolean flag = true;

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

        Thread t = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("Thread Interrupt Flag:"+Thread.currentThread().isInterrupted());
                while (flag){

                }
                System.out.println("sign flag by:" + flag);
                System.out.println("Thread Interrupt Flag:"+Thread.currentThread().isInterrupted());
                System.out.println("I'm still working on it");
            }
        });

        t.start();
        Thread.sleep(100);
        flag = false;
        t.interrupt();
    }
}

Run result:

Thread interrupt flag: false
 flag is: false
 Thread interrupt flag:true
 I'm still working on it

When the thread starts without calling the interrupt method, the interrupt state is false, then the interrupt method is called, and the flag is set to false.At this point, the run method jumps out of the while dead loop.We will find that the interrupt state of the thread is true, but the thread will continue executing until the end of execution.

sleep response interruption

Common blocking methods in threads, such as sleep, join, and wait, all respond to interrupts and throw an InterruptedException exception.Note, however, that the interrupt state of the thread is cleared.So when we catch an interrupt exception, we should keep the interrupt information so that the upper code knows that the current thread is interrupting.There are usually two ways to do this.

One is to catch the exception and then throw it again so that the upper code knows it.Another is to reset the interrupt state to true by using the interrupt method when an exception is caught.

Next, take the sleep method as an example, catch the interrupt exception, and reset the interrupt state:

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

        Thread t = new Thread(new Runnable() {
            private int count = 0;
            @Override
            public void run() {
                try {
                    count = new Random().nextInt(1000);
                    count = count * count;
                    System.out.println("count:"+count);
                    Thread.sleep(5000);
                } catch (Exception e) {
                    System.out.println(Thread.currentThread().getName()+"Thread first interrupt flag:"+Thread.currentThread().isInterrupted());
                    //Re-set thread interrupt state to true for upper code to determine
                    Thread.currentThread().interrupt();
                    System.out.println(Thread.currentThread().getName()+"Thread second interrupt flag:"+Thread.currentThread().isInterrupted());
                }
            }
        });

        t.start();

        Thread.sleep(100);
        t.interrupt();
    }
}

Result:

count:208849
Thread-0 Thread first interrupt flag: false
Thread-0 Thread second interrupt flag: true

Introduction to LockSupport method

Two important methods in the LockSupport method are park and unpark.

park and interrupt interrupts

The Park method can block the current thread and is returned from the park method if the unpark method is called or the current thread is interrupted.

The Park method's response to the interrupt method is somewhat different from that of sleep.Instead of throwing an interrupt exception, it returns directly from the park method without affecting the thread's continued execution.Let's look at the code:

public class LockSupportTest {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new ParkThread());
        t.start();
        Thread.sleep(100); //①
        System.out.println(Thread.currentThread().getName()+"Start waking up blocked threads");
        t.interrupt();
        System.out.println(Thread.currentThread().getName()+"End wake-up");

    }
}

class ParkThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Start Blocking");
        LockSupport.park();
        System.out.println(Thread.currentThread().getName()+"First End Blocking");
        LockSupport.park();
        System.out.println("Second End Block");
    }
}

The printout is as follows:

Thread-0 Begins Blocking
 main starts waking up blocked threads
 main end wake up
 Thread-0 First End Blocking
 Second End Block

When the interrupt method is called, the interrupt state is set to true, and the park method determines the interrupt state. If true, it returns directly and continues execution without throwing an exception.Note that the interrupt flag is not cleared here.

unpark

Unpark wakes the specified thread of the park.However, it is important to note that unpark is not an easy way to wake up a thread that is parked directly.Look at JDK's explanation:

Unpark only sets a license for the current thread.If the current thread has been blocked (that is, the park has been called), it will become unblocked.Otherwise, the next time the park method is called, it is guaranteed not to block.This means that the order in which parks and unparks are invoked does not matter. As long as unpark sets this license, the park method can consume the license at any time without blocking the method.

It is also important to note that there is at most one license, that is, even if the unpark method is invoked multiple times, the license will not be added.We can pass the code validation by just modifying the top line:

//LockSupportTest class
//Original Code
t.interrupt();
//Modify to
LockSupport.unpark(t);
LockSupport.unpark(t);

You will find that only the first blocking will wake up, but the second will continue blocking.The results are as follows:

Thread-0 Begins Blocking
 main starts waking up blocked threads
 main end wake up
 Thread-0 First End Blocking

In addition, on this basis, the sleep method of the main thread is removed (1 in the code) so that the main thread runs first, that is, it is possible to call the unpark method before the child thread starts calling the park method to block.We will find that the following results prove that the park method and unpark described above are sequential and that the park method can consume licenses at any time.

main starts waking up blocked threads
 main end wake up
 Thread-0 Begins Blocking
 Thread-0 First End Blocking

park/unpark and wait/notify differences

Once you understand the use of park/unpark, you must also be able to analyze how they differ from wait and notify.

1) The wait and notify methods must be used with synchronized lock.park/unpark is flexible and can be used anywhere without this limitation.

2) Without sequencing when using park/unpark, threads can be unblocked (the previous code has been validated).Wat must be used before notify, and if notify before wait, the thread will wait all the time.

3) Notfy can only randomly release one thread, not a specific thread. NotfyAll is all the threads in the release lock object.The unpark method wakes up the specified thread.

4) Calling the wait method frees the lock resource for the current thread, but only if the lock has been acquired.The park does not release the lock resource.(code validation below)

public class LockSyncTest {
    private static Object lock = new Object();
    //Save the thread calling park for subsequent wakeups
    private static Thread parkedThread;

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

        Thread t1 = new Thread(()->{
             synchronized (lock){
                 System.out.println("unpark Front");
                 LockSupport.unpark(parkedThread);
                 System.out.println("unpark after");
             }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //When the same lock is used as the t1 thread, the park will not release the lock resource, and if it is replaced with this lock, the lock will be released.
                synchronized (lock){
                    System.out.println("park Front");
                    parkedThread = Thread.currentThread();
                    LockSupport.park();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("park after");
                }
            }
        });

        t2.start();
        Thread.sleep(100);
        t1.start();

    }
}
//Print results
//Before park

The above code will remain stuck on the t2 thread because park will not release the lock and t1 will not execute.

If the lock of t2 is replaced with this lock, that is, as long as it is not the same lock as t1, T1 will execute normally and then wake up the t2 thread.The printout is as follows:

Before park
 Before unpark
 After unpark
 After park

Posted by gigantorTRON on Sun, 01 Mar 2020 08:27:23 -0800