preface
In the spirit of “know the why and know the why”, this article will analyze how interrupts work from the underlying source code. Through this article, you will learn:
1, the Thread of the underlying source entry 2, interrupt the effect of 3, Thread. Sleep/Object. The join/Object. Wait for interrupt handling 4, how can stop a Thread 5, difficult point analysis
1, thread bottom source entry
Java Thread
Take the example of the thread.java method thread.interrupt (), which ultimately calls Interrupt0 () :
private native void interrupt0();
Copy the code
As you can see, it is a native method, so let’s see how to find its JNI implementation.
JNI entrance
Thread. C defines the JNI methods:The Interrupt0 () method corresponds to the JVM_Interrupt() function. The JVM. CPP in:
#jvm.cpp
JVM_ENTRY(void, JVM_Interrupt(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_Interrupt");
oop java_thread = JNIHandles::resolve_non_null(jthread);
MutexLockerEx ml(thread->threadObj() == java_thread ? NULL : Threads_lock);
JavaThread* thr = java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread));
if (thr != NULL) {
//调用Thread.cpp里的函数
Thread::interrupt(thr);
}
JVM_END
Copy the code
Follow up with Thread. CPP:
# thread.cpp void thread.interrupt (thread.cpp) {// Call OS ::interrupt(thread.cpp); }Copy the code
I end up calling a function in OS.
2, the role of interruption
Interrupt source code analysis
We found the entrance, and we continued our analysis. OS :: Interrupt (thread) OS :: Interrupt (thread) OS :: Interrupt (thread) OS ::interrupt(thread)
#os_linux.cpp void os::interrupt(Thread* thread) { ... OSThread* osthread = thread->osthread(); // Interrupt flag bit is not set if (! Osthread ->interrupted()) {// set the interrupt bit to true osthread->interrupted(true); OrderAccess::fence(); ParkEvent * const slp = thread->_SleepEvent ; // Wake up the thread, corresponding to sleep suspended if (SLP! = NULL) slp->unpark() ; If (thread->is_Java_thread()) ((JavaThread*)thread)->parker()->unpark(); ParkEvent * ev = thread->_ParkEvent; if (ev ! = NULL) ev->unpark() ; }Copy the code
Obviously, calling thread.interrupt () in the Java layer ends up doing two things at the bottom:
1. Set interrupt flag bit to true. 2. Wake up the suspended thread.
Interrupt Status Query
Thread. Java provides two methods to query the value of interrupt flag bits:
#Thread. Java // Member method public Boolean isInterrupted() {return interrupted (false); } public static Boolean interrupted() {return currentThread().isinterrupted (true); }Copy the code
Either a member or a static method ends up calling Thread.isinterrupted (xx) :
#Thread. Java //ClearInterrupted Private native Boolean isInterrupted(Boolean ClearInterrupted);Copy the code
As can be seen:
1. The member method isInterrupted() does not clear the interrupt flag bit. 2, static method interrupted() clears the interrupt flag bit.
Following up with the isInterrupted(xx) method, the import experience traced above ends up with the following code:
#Thread.cpp bool Thread::is_interrupted(Thread* thread, bool clear_interrupted) { return os::is_interrupted(thread, clear_interrupted); } #os_linux.cpp bool os::is_interrupted(Thread* thread, bool clear_interrupted) { OSThread* osthread = thread->osthread(); Bool interrupted = osthread->interrupted(); If (interrupted && clear_interrupted) {// If clear_interrupted is true, Osthread ->set_interrupted(false); } // Return interrupted; }Copy the code
Thus, the thread.isinterrupted (xx) method does this:
1, query the current thread interrupt flag bit value. 2. Determine whether to reset the interrupt flag according to the parameters.
Whereas Thread.isinterrupted (xx) is a private method, thread.java provides two methods: Thread.isinterrupted ()(member method) and Thread.interrupted(static method).
3, Thread. Sleep/Object. The join/Object. Wait for interrupt handling
Interrupt thread Demo
Now that you know what interrupts do, let’s see how to interrupt a Thread with thread.interrupt (), starting with a Demo.
public class TestThread { public static void main(String args[]) { Thread t1 = new Thread(new Runnable() { @Override public void run() { while (true) { System.out.println("t1 is alive " + System.currentTimeMillis()); }}}); t1.start(); Thread.sleep(2000); // Interrupt t1 t1.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } while (true) { System.out.println("t1 interrupt status:" + t1.isInterrupted() + " t1 isAlive:" + t1.isAlive() + " " + System.currentTimeMillis()); }}}Copy the code
The purpose of this Demo is to:
1. Start thread T1 first and let T1 continuously cycle the printing time. 2, interrupt T1 in another thread (main thread), and constantly query the value of T1 interrupt flag.
Without analyzing the interrupt principle above, you might think that T1 should be interrupted out of the loop. In fact, the print shows that t1’s interrupt flag bit is set to true, but T1 does not exit the loop. This result is consistent with our analysis of principles, because there are only two things that the interrupt thread does underneath: set the interrupt flag bit and wake up the thread. In the above example, T1 is not suspended, so waking up the thread makes little sense. To sum up:
The word “abort thread” sounds like an overbearing phrase that mistakenly means stopping a thread. In fact, it does not control whether a thread stops or not, but whether the thread exits or has an exception thrown during execution.
Modify the above Demo slightly as follows:
public class TestThread { public static void main(String args[]) { Thread t1 = new Thread(new Runnable() { @Override public void run() { try { while (true) { System.out.println("t1 is alive " + System.currentTimeMillis()); Thread.sleep(1000); } } catch (InterruptedException e) { System.out.println("t1 catch exception:" + e.getMessage()); }}}); t1.start(); Thread.sleep(2000); // Interrupt t1 t1.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } while (true) { System.out.println("t1 interrupt status:" + t1.isInterrupted() + " t1 isAlive:" + t1.isAlive() + " " + System.currentTimeMillis()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }}}}Copy the code
Compared to the previous Demo, just increase the sleep time by 1s in T1, and print the following result:As you can see, t1 is interrupted and exits the loop due to an exception thrown, where the break flag bit is false and the thread has stopped running.
Thread. Sleep source code parsing
Comparing the above two demos shows that the difference is due to the addition of thread.sleep (xx), so let’s see what’s going on inside it.
#jvm.cpp JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis)) JVMWrapper("JVM_Sleep"); If (Thread::is_interrupted (Thread, true) &&! HAS_PENDING_EXCEPTION) { THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted"); }... if (millis == 0) { ... } else { ThreadState old_state = thread->osthread()->get_state(); thread->osthread()->set_state(SLEEPING); OS_INTRPT) {if (OS ::sleep(thread, millis, true) == OS_INTRPT) { THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep Interrupted "); } } thread->osthread()->set_state(old_state); }... JVM_END #os_linux.cpp int os::sleep(Thread* thread, jlong millis, bool interruptible) { assert(thread == Thread::current(), "thread consistency check"); //_SleepEvent Is a ParkEvent type. ParkEvent * const SLP = thread->_SleepEvent; . // Interruptible Specifies whether an interrupt is supported. By default, if (interruptible) {... for (;;) If (OS ::is_interrupted(thread, true)) {return or break if (OS ::is_interrupted(thread, true)) { If yes, return OS_INTRPT. } if(millis <= 0) {return OS_OK; }... {// Suspend thread SLP ->park(millis); }}} else {for (;;) {... If (millis <= 0) break; prevtime = newtime; slp->park(millis); } return OS_OK ; }}Copy the code
Thread.sleep(xx) ¶
1. The thread suspends for a certain amount of time and resumes the loop when the time is up. Interruptible ==true The loop checks whether an interrupt has occurred and throws an interrupt exception if it does.
Let’s look at the Demo above:
1. The Thread calls thread.sleep (xx) to sleep, and the Thread is suspended. 2. An external call to thread.interrupt () interrupts the Thread, with the interrupt flag value true. 3. The thread is woken up to determine whether an interrupt has occurred (via the interrupt flag bit), and if so, throws an exception.
Although thread.interrupt () does not stop the Thread directly, you can use the interrupt flag bit to see if interrupt action has occurred, and use that action to decide whether to stop the Thread execution. For Thread.sleep(xx), as a public method, when an interrupt is detected, an interrupt exception is thrown for external handling.
Thread.sleep(xx) is called by thread.interrupt () to catch an interrupt exception. Where does this change the value of the interrupt flag bit false? The following code is used to determine the interrupt mark:
os::is_interrupted(thread, true)
Copy the code
The second argument resets the value of the current interrupt flag bit, as analyzed above.
Call Object.join/ object. wait to suspend the Thread. If the Thread is interrupted, it will behave like Thread.sleep and will not be analyzed here.
4. How exactly do YOU stop a thread
Let’s look at Demo:
public class TestThread { static volatile boolean isCancelThread = false; public static void main(String args[]) { Thread t1 = new Thread(new Runnable() { @Override public void run() { while(! isCancelThread || ! Thread.currentThread().isInterrupted()) { try { doSomething1(); doSomething2(); } catch (Exception e) { if (e instanceof InterruptedException) { isCancelThread = true; }}}}}); t1.start(); Interrupt ()} private static void doSomething1() {private static void doSomething1()} private Static void doSomething2() {// doSomething2()}Copy the code
To parse the Demo logic: doSomething1() and doSomething2() can be self-written methods, or methods provided by someone else, and they can internally block a thread.
First: The thread is externally flagged by setting isCancelThread to stop running. A drawback to this approach is that if a thread blocks when calling doSomething1() or doSomething2(), it will not immediately detect the value of isCancelThread, meaning that it will not immediately stop the thread.
The second problem is that doSomething1() or doSomething2() can block. The external means to interrupt a Thread by using Thread.interrupt(), at which point an exception needs to be caught. Catching the interrupt means that the Thread can be stopped from running. Thread.currentthread ().isinterrupted () If you don’t want to use the isCancelThread flag, you can check whether the interrupt flag is thread.currentThread ().isinterrupted ().
while(! Thread.currentThread().isInterrupted()) { try { doSomething1(); doSomething2(); } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); }}}Copy the code
Why add an interrupt action to a catch? The reason is that sleep/wait/ Join and other methods may set the interrupt flag bit to false during the interrupt. The interrupt is repeated to let the while sense that the interrupt has occurred and exit the loop.
5. Difficult point analysis
Some articles on the web summarize whether threads can be interrupted as follows:
1. Interrupt cannot interrupt a thread if it is in a RUNNABLE state. Interrupt interrupts a thread if it is WAITING/WAITING.
From the above analysis, you already know that this statement is not rigorous. Let’s look at a Demo where Thread interrupts with interrupt() while Thread is RUNNABLE:
public class TestThread { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { @Override public void run() { try { doSomething(); } catch (InterruptedException e) { e.printStackTrace(); }}}); t1.start(); Thread.sleep(2000); t1.interrupt(); System.out.println("main thread interrupt Thread t1"); } private static void doSomething() throws InterruptedException{ while(true) { System.out.println("thread state:" + Thread.currentThread().getState() + " " + System.currentTimeMillis()); if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); }}}}Copy the code
Thread1 has obviously been in a RUNNABLE state, but Thread1 stops when interrupt() is called. Let’s look at a Demo where Thread is WAITING and interrupt() is called:
public class TestThread { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { @Override public void run() { try { doSomething(); } catch (InterruptedException e) { e.printStackTrace(); }}}); t1.start(); long count = 100000; while(count >= 0) { System.out.println("thread state:" + t1.getState() + " " + System.currentTimeMillis()); count--; } t1.interrupt(); System.out.println("main thread interrupt Thread t1"); while (true) { Thread.sleep(1000); System.out.println("after interrupt thread state:" + t1.getState() + " " + System.currentTimeMillis()); } } private static void doSomething() throws InterruptedException{ while(true) { LockSupport.park(); }}}Copy the code
Thread1 is clearly in a WAITING state, but Thread1 does not stop after interrupt() is called. The root of the problem is:
Whether a thread stops depends on whether the code in the thread’s execution body (method/block) detects the interrupt and then handles it (throws an exception/returns the normal flow).
Therefore, it is more precise to say that whether a thread can be interrupted depends on whether it detects and handles the interrupted state. This is fully reflected when implementing interruptible/non-interruptible locks in AQS.
This article is based on JDK 1.8 and the runtime environment is JDK1.8.