JAVA interrupt mechanism

Meaning of break

Some people say that interrupts mean stopping a thread. How does that explain why interrupts can be used to wake up a blocked thread?Some people say interruption means waking up a thread, but many times we do use it to stop a thread.What's going on here?

In fact, interruption means: to change the state of a thread by force, usually by throwing an exception, to force the CPU to execute. Whether the thread wakes up or stops after changing the state depends on the following logic:

  • If you catch exceptions in the catch block and do nothing, then the thread is awakened.
  • If you keep throwing exceptions up, or exit gracefully in the catch block, you'll stop the thread.

Interruption mechanism

java does not provide any means of directly interrupting a thread, it only provides an interrupt mechanism.That is, thread A makes a request to thread B to "change your state" (thread B can send this request to itself), but thread B does not stop running immediately, but chooses the appropriate time to respond to the interruption in its own way, or it can ignore the interruption.

That is, java interrupts do not directly control threads, they need to be interrupted threads to decide what to do, like parents telling their children outside to pay attention to their bodies, but it is entirely up to them whether or not they pay attention to their bodies and how they pay attention to them.

Interrupt related methods

interrupt() method and basic concepts

Interrupt status: Remember the term interrupt mechanism works around this field.The field representing the break state in Java source code is in the Thread class:

private volatile Interruptible blocker;

There is no need for in-depth analysis of the "Interruptible" class, and there are a few operations on the "blocker" variable.

Default blocker=null; < 1

2. Calling the method "interrupt0();" will result in "the interrupt state of the thread will be set (terminology in JDK documentation)".# 2

3. Re-invoking "interrupt0();" will result in "its interrupt state will be cleared (as termed in JDK documentation)"< 3

The interrupt() method does not interrupt a running thread, and when your thread is running normally, it calls t.interrupt and does nothing but flag the thread.Of course, if you have a flag in the run method that determines this, if there is logic to gracefully exit when the break is true, or if you throw an InterruptException exception like many jdk implementations and use the exception to end the thread, that's another matter.

"Interrupt" is a mistake. At the language level, we don't really interrupt a running thread, but interrupts do "interrupt lightweight blocking" or "wake up lightweight blocking".

"Lightweight blocking" is the thread blocking caused by calling the wait(), wait(long) or wait(long, int) methods of the Object class, or the join(), join(long), join(long, int), sleep(long), or sleep(long, int) methods of the thread class.The thread is in the Waiting state, responds to t.interrupt, interrupts the blocking, and throws an InterruptException exception directly.

Why can't calling interrupt() interrupt the thread?

Let's look at the interrupt method source

/**
     * Interrupts this thread.
     *
     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.
     *
     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *
     * If a thread is blocked in object.wait, Thread.join, and Thread.sleep, it throws an InterruptedException and clears the thread's interrupt state.
     *
     *
     * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt
     * status will be set, and the thread will receive a {@link
     * java.nio.channels.ClosedByInterruptException}.
     *

     *If the thread is blocked on an I/O operation of InterruptibleChannel, calling this method will close the channel and set the interrupt state.
     *And the thread receives the ClosedByInterruptException exception
     *
     * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
     * then the thread's interrupt status will be set and it will return
     * immediately from the selection operation, possibly with a non-zero
     * value, just as if the selector's {@link
     * java.nio.channels.Selector#wakeup wakeup} method were invoked.
     *
     *If the thread is blocked on a Selector in nio, calling this method will set the interrupt state.
     *And the thread returns immediately with a non-zero value (that is, it calls the java.nio.channels.Selector#wakeup method)
     *
     *
     * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>
     *
     * <p> Interrupting a thread that is not alive need not have any effect.
     *
     * @throws  SecurityException
     *          if the current thread cannot modify this thread
     *
     * @revised 6.0
     * @spec JSR-51
     */
public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag, just set the flag
            // Interruptible's interrupt method has its own implementation in the AbstractInterruptibleChannel and AbstractSelector classes
            //So interrupt works for both scenarios, as the comment for the current method says (the Chinese translation part of the current method comment)
            //But for normal threads, the vast majority are not the interrupt scenarios above.
            //Because thread s are not injected by default as implementations of the AbstractInterruptibleChannel and AbstractSelector classes
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

This is the code from the Java source so we can see the answer to the question.The thread's blocker field (interrupt status) defaults to null(? 1).When the interrupt() method is called, only interrupt0() (setting the interrupt flag) is run and the if statement is not entered, so the code b.interrupt() that actually executes the interrupt is not called.

The interrupt() method causes the thread to end blocking prematurely

Why does the interrupt() method cause the thread to end blocking prematurely?We use the sleep method to block threads

/**
 * Causes the currently executing thread to sleep (temporarily cease
 * execution) for the specified number of milliseconds, subject to
 * the precision and accuracy of system timers and schedulers. The thread
 * does not lose ownership of any monitors.
 *
 * @param  millis
 *         the length of time to sleep in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public static native void sleep(long millis) throws InterruptedException;

This local method sets the blocker to 2, so the interrupt method is called and the statement in if is executed:

if (b != null) {
    interrupt0();           // Just to set the interrupt flag
    b.interrupt(this);
    return;
}

Where, interrupt0(); method clears the interrupt flag, which is set to #3.Then call the core statement b.interrupt(this), which really interrupts the thread, and throws the InterruptedException.

Let's see why b.interrupt(this) interrupts threads.

In addition to the two implementations of Interruptible.interrupt mentioned above in the AbstractInterruptibleChannel and AbstractSelector classes, the Interruptible b we injected is not an Interruptible implementation in the AbstractInterruptibleChannel and AbstractSelector classes for normal Thead classes.(These two classes are implemented only in a specific scenario, as follows: Interrupt implementation in the AbstractInterruptibleChannel class)

Looking at the Thread.blockedOn method, Thread itself does not initialize its blocker field, and the blocker value is injected in.

/* Set the blocker field; invoked via sun.misc.SharedSecrets from java.nio code
*/
void blockedOn(Interruptible b) {
	synchronized (blockerLock) {
		blocker = b;
	}
}

The comment clearly states that this method is called by via sun.misc.SharedSecrets, and the interrupt(this) method executed by injected b is actually a native method, defined in jvm.cpp:

JVM_ENTRY(void, JVM_Interrupt(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_Interrupt");

  // Ensure that the C++ Thread and OSThread structures aren't freed before we operate
  oop java_thread = JNIHandles::resolve_non_null(jthread);
  MutexLockerEx ml(thread->threadObj() == java_thread ? NULL : Threads_lock);
  // We need to re-resolve the java_thread, since a GC might have happened during the
  // acquire of the lock
  JavaThread* thr = java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread));
  if (thr != NULL) {
    Thread::interrupt(thr);
  }
JVM_END

JVM_Interrupt checks the parameters and calls Thread::interrupt:

void Thread::interrupt(Thread* thread) {
  trace("interrupt", thread);
  debug_only(check_for_dangling_thread_pointer(thread);)
  os::interrupt(thread);
}

Thread::interrupt calls the os::interrupt method implementation, and the os::interrupt method is defined in os_linux.cpp:

void os::interrupt(Thread* thread) {
  assert(Thread::current() == thread || Threads_lock->owned_by_self(),
    "possibility of dangling Thread pointer");

  //Get System native Thread Object
  OSThread* osthread = thread->osthread();

  if (!osthread->interrupted()) {
    osthread->set_interrupted(true);
   //Memory barrier to make the interrupted state of osthread immediately visible to other threads
    OrderAccess::fence();
    //As mentioned earlier, _SleepEvent is used for Thread.sleep, and threads that call the sleep method wake up via unpark
    ParkEvent * const slp = thread->_SleepEvent ;
    if (slp != NULL) slp->unpark() ;
  }

  //_parker is used for concurrent-related locks, which are also awakened here by unpark
  if (thread->is_Java_thread())
    ((JavaThread*)thread)->parker()->unpark();
  //Synchronized synchronized block and Object.wait() wake-up
  ParkEvent * ev = thread->_ParkEvent ;
  if (ev != NULL) ev->unpark() ;

}

Thus, interrupt actually wakes up an object through the unpark method of ParkEvent;

isInterrupted() and interrupted() methods

  • isInterrupted() simply determines if it has been marked with an interrupt
  • The interrupted() method determines if it has been marked with an interrupt.And clear the thread's interrupt state

Next, let's talk about the similarities and differences between the "interrupted()" and "isInterrupted()" methods.Before that, take a look at the code for the two methods in the source code, as follows:

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
    return isInterrupted(false);
}
 /**
 * Tests if some Thread has been interrupted.  The interrupted state
 * is reset or not based on the value of ClearInterrupted that is
 * passed.
 */
private native boolean isInterrupted(boolean ClearInterrupted);

The same point is to determine if the thread's interrupt status is set, and if it is set to return true, otherwise false. There are two differences:

  • One: the former is the static method, the caller is the current thread, the latter is the common method, and the caller is this current.
  • Two: They actually call a native method in Java, isInterrupted(boolean ClearInterrupted); the difference is that the former passes in the parameter true, and the latter passes in false.
  • The meaning is that the former clears the thread's interrupt state(? 3) and calls to the latter thread's interrupt state are unaffected.

Interrupt implementation in AbstractInterruptibleChannel class

If a nio channel implements the InterruptibleChannel interface, it can respond to interrupt() interrupts by implementing the abstract implementation of the InterruptibleChannel interface in the method begin() of the AbstractInterruptibleChannel class:

protected final void begin() {
        if (interruptor == null) {
            interruptor = new Interruptible() {
                    public void interrupt(Thread target) {
                        synchronized (closeLock) {
                            if (!open)
                                return;
                            open = false;
                            interrupted = target;
                            try {
                                AbstractInterruptibleChannel.this.implCloseChannel();
                            } catch (IOException x) { }
                        }
                    }};
        }
        blockedOn(interruptor);//Set the current thread's blocker to interruptor
        Thread me = Thread.currentThread();
        if (me.isInterrupted())
            interruptor.interrupt(me);
    }

 protected final void end(boolean completed)
        throws AsynchronousCloseException
    {
        blockedOn(null);//Set the current thread's blocker to null
        Thread interrupted = this.interrupted;
       //If an interrupt occurs, the Thread.interrupt method calls the interrupt method of Interruptible.
      //Set this.interrupted as the current thread
        if (interrupted != null && interrupted == Thread.currentThread()) {
            interrupted = null;
            throw new ClosedByInterruptException();
        }
        if (!completed && !open)
            throw new AsynchronousCloseException();
    }

For example, with the above code, the ReadableByteChannel on the nio channel executes begin() every time before the blocking method read() is executed, registering the Interruptible callback interface with the current thread.When the thread interrupts, Thread.interrupt() triggers the callback interface Interruptible to close the io channel, causing the read method to return, and finally executes the end() method in the final block to check the interrupt flag, throwing ClosedByInterruptException;

Interrupt implementation in AbstractSelector class

//java.nio.channels.spi.AbstractSelector
	protected final void begin() {
	if (interruptor == null) {
		interruptor = new Interruptible() {
			public void interrupt(Thread ignore) {
				AbstractSelector.this.wakeup();
		}};
	}
	AbstractInterruptibleChannel.blockedOn(interruptor);
	Thread me = Thread.currentThread();
	if (me.isInterrupted())
		interruptor.interrupt(me);
}
protected final void end() {
    AbstractInterruptibleChannel.blockedOn(null);
}
//sun.nio.ch.class EPollSelectorImpl
protected int doSelect(long timeout) throws IOException {
	......
	try {
		begin();
		pollWrapper.poll(timeout);
	} finally {
		end();
	}
	......
}

Handling of interrupts

Since the Java interrupt mechanism only sets the interrupt state of the interrupted thread, what should the interrupted thread do?

Management of Interrupt Status

  • Generally speaking, when an InterruptedException is thrown in a method declaration that may be blocked, it implies that the method is interruptible, such as BlockingQueue#put, BlockingQueue#take, Object#wait, Thread#sleep, etc., if the program captures the InterruptedException thrown by these interruptable blocking methods or detects the interruptionWhat should I do with the interrupt information?There are two general principles:

    • If you encounter an InterruptedException thrown by an interruptable blocking method, you can continue to throw the exception to the top of the method call stack
    • If an interrupt is detected, the interrupted state can be cleared and the InterruptedException thrown, making the current method an interruptable method as well.
  • If it is sometimes not convenient to throw an InterruptedException on a method, such as if there is no throws InterruptedException on the method signature in an interface you are implementing, then you can capture the InterruptedException of an interruptable method and reset the interrupt state through Thread.currentThread.interrupt().This is also true if the interrupt state is detected and cleared.

In general code, especially as a base class library, interrupts should never be swallowed (i.e., do nothing in the catch after capturing the InterruptedException, clear the interrupt state without resetting it, throw the InterruptedException, and so on).Because swallowing the interrupt state causes this information to be unavailable to the upper layer of the method call stack.

Of course, there are always exceptions to this. You can do this when you know exactly who will call your method and the caller won't have trouble swallowing the interrupt.

  • All in all, you want the upper layer of the method call stack to be aware of the break.Suppose you have written a class library that contains a method, amethod, that detects and clears interrupt states in amethod without throwing out an InterruptedException. As a user of amethod, he does not know the details. If a user wants to use interrupts to do something after calling amethod, he calls amethodThen he will never detect the interrupt, because the interrupt information has been cleared by amethod.

Interrupted response

  • What should I do when an interrupt is found in the program?It depends on the actual situation.Some programs may terminate the thread as soon as an interrupt is detected, others may quit the current task and continue with the next one...As a collaboration mechanism, this is negotiated with the interrupt party. When interrupt is called, everything is known beforehand, such as doing some transaction rollback, some cleanup, some compensation, and so on.You should not interrupt a thread if you are not sure what response it will respond to after calling its interrupt.

Tags: Programming Java JDK jvm

Posted on Wed, 28 Aug 2019 10:50:48 -0700 by Chris Mayo