“This is the 24th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

The article brief introduction

This article mainly focuses on the operation related to thread state control to analyze the principle of thread, such as thread interruption, thread communication, etc. The content is more, and may be divided into two articles

Content navigation

  1. The implementation principle of thread start
  2. Analysis of the implementation principle of thread stop
  3. Why does an interrupt thread throw InterruptedException

How threads are started

Before we briefly analyzed the use of threads, by calling the thread’s start method to start the thread, the thread will call the run method to execute the business logic, after the execution of the run method, the life cycle of the thread is terminated. When you first learned about threads, you might wonder why you call the start method instead of the run method to start a thread

public class Thread implements Runnable {...public synchronized void start(a) {
        /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */
        if(threadStatus ! =0)
            throw new IllegalThreadStateException();
        /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */
        group.add(this);
        boolean started = false;
        try {
            start0(); // Notice here
            started = true;
        } finally {
            try {
                if(! started) { group.threadStartFailed(this); }}catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */}}}private native void start0(a);// Notice here.Copy the code

Start0 () is registered in the static block of Thread, as shown in the following code


public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives(a);
    static {
        registerNatives();
    }

Copy the code

The registerNatives function is to register some local methods provided to the Thread class for use, such as start0(), isAlive(), currentThread(), and sleep(); These are all familiar methods. The registerNatives local method is defined in the thread. c file. Thread.c defines the common data and operations for each operating system platform. The following is the full content of Thread.c

static JNINativeMethod methods[] = {
    {"start0"."()V",        (void *)&JVM_StartThread},
    {"stop0"."(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive"."()Z",        (void *)&JVM_IsThreadAlive},
    {"suspend0"."()V",        (void *)&JVM_SuspendThread},
    {"resume0"."()V",        (void *)&JVM_ResumeThread},
    {"setPriority0"."(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield"."()V",        (void *)&JVM_Yield},
    {"sleep"."(J)V",       (void *)&JVM_Sleep},
    {"currentThread"."()" THD,     (void *)&JVM_CurrentThread},
    {"countStackFrames"."()I",        (void *)&JVM_CountStackFrames},
    {"interrupt0"."()V",        (void *)&JVM_Interrupt},
    {"isInterrupted"."(Z)Z",       (void *)&JVM_IsInterrupted},
    {"holdsLock"."(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads"."()" THD,   (void *)&JVM_GetAllThreads},
    {"dumpThreads"."([" THD "The [[" STE, (void *)&JVM_DumpThreads},
    {"setNativeName"."(" STR ")V", (void *)&JVM_SetNativeThreadName},
};
#undef THD
#undef OBJ
#undef STE
#undef STR
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

Copy the code

As you can see from this code, start0() actually executes the JVM_StartThread method. From the name, it seems that a thread is started at the JVM level, and if so, the JVM level must call the Run method defined in Java. Well, keep going and find out. We find the jvm.cpp file; You need to download hotspot’s source code to find this file.


JVM_ENTRY(void.JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread"); . native_thread =new JavaThread(&thread_entry, sz); .Copy the code

JVM_ENTRY is used to define the JVM_StartThread function, which creates a truly platform-specific local thread. In the interest of breaking the casserore, continue to look at what newJavaThread is doing, and continue to look for the JavaThread definition in the hotspot source thread. CPP file at line 1558

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
  Thread(a)#if INCLUDE_ALL_GCS
  , _satb_mark_queue(&_satb_mark_queue_set),
  _dirty_card_queue(&_dirty_card_queue_set)
#endif // INCLUDE_ALL_GCS
{
  if (TraceThreadEvents) {
    tty->print_cr("creating thread %p".this);
  }
  initialize(a); _jni_attach_state = _not_attaching_via_jni;set_entry_point(entry_point);
  // Create the native thread itself.
  // %note runtime_23
  os::ThreadType thr_type = os::java_thread;
  thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
                                                     os::java_thread;
  os::create_thread(this, thr_type, stack_sz);
  _safepoint_visible = false;
  // The _osthread may be NULL here because we ran out of memory (too many threads active).
  // We need to throw and OutOfMemoryError - however we cannot do this here because the caller
  // may hold a lock and all locks must be unlocked before throwing the exception (throwing
  // the exception consists of creating the exception object & initializing it, initialization
  // will leave the VM via a JavaCall and then all locks must be unlocked).
  //
  // The thread is still suspended when we reach here. Thread must be explicit started
  // by creator! Furthermore, the thread must also explicitly be added to the Threads list
  // by calling Threads:add. The reason why this is not done here, is because the thread
  // object must be fully initialized (take a look at JVM_Start)
}

Copy the code

This method takes two parameters. The first parameter is the name of the function to which the thread will call the corresponding function after it is successfully created. The second is the number of threads already in the current process. Finally, we’ll focus on OS ::create_thread, which actually creates a thread by calling the platform’s thread creation method. The next step is to start the Thread by calling the Thread::start(Thread* Thread) method in the thread. CPP file


void Thread::start(Thread* thread) {
  trace("start", thread);
  // Start is different from resume in that its safety is guaranteed by context or
  // being called from a Java method synchronized on the Thread object.
  if(! DisableStartThread) {if (thread->is_Java_thread()) {
      // Initialize the thread state to RUNNABLE before starting this thread.
      // Can not set it after the thread started because we do not know the
      // exact thread state at that time. It could be in MONITOR_WAIT or
      // in SLEEPING or some other state.
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    os::start_thread(thread); }}Copy the code

The start method has a function call: OS ::start_thread(thread); , calling the platform method that starts the Thread, which eventually calls the JavaThread:: Run () method in thread.cpp


// The first routine called by a new Java thread
void JavaThread::run(a) {
  // initialize thread-local alloc buffer related fields
  this->initialize_tlab(a);// used to test validitity of stack trace backs
  this->record_base_of_stack_pointer(a);// Record real stack base and size.
  this->record_stack_base_and_size(a);// Initialize thread local storage; set before calling MutexLocker
  this->initialize_thread_local_storage(a);this->create_stack_guard_pages(a);this->cache_global_variables(a);// Thread is now sufficient initialized to be handled by the safepoint code as being
  // in the VM. Change thread state from _thread_new to _thread_in_vm
  ThreadStateTransition::transition_and_fence(this, _thread_new, _thread_in_vm);
  assert(JavaThread::current() = =this."sanity check");
  assert(! Thread::current() - >owns_locks(), "sanity check");
  DTRACE_THREAD_PROBE(start, this);
  // This operation might block. We call that after all safepoint checks for a new thread has
  // been completed.
  this->set_active_handles(JNIHandleBlock::allocate_block());
  if (JvmtiExport::should_post_thread_life()) {
    JvmtiExport::post_thread_start(this);
  }
  EventThreadStart event;
  if (event.should_commit()) {
     event.set_javalangthread(java_lang_Thread::thread_id(this->threadObj()));
     event.commit(a); }// We call another function to do the rest so we are sure that the stack addresses used
  // from there will be lower than the stack base just computed
  thread_main_inner(a);// Note, thread is no longer valid at this point!
}


Copy the code

This method does a series of initializations and ends with a thread_main_inner method. What is the logic of this method


void JavaThread::thread_main_inner(a) {
  assert(JavaThread::current() = =this."sanity check");
  assert(this->threadObj() != NULL."just checking");
  // Execute thread entry point unless this thread has a pending exception
  // or has been stopped before starting.
  // Note: Due to JVM_StopThread we can have pending exceptions already!
  if (!this->has_pending_exception() &&
      !java_lang_Thread::is_stillborn(this->threadObj{{()))ResourceMark rm(this);
      this->set_native_thread_name(this->get_thread_name());
    }
    HandleMark hm(this);
    this->entry_point() (this.this);
  }
  DTRACE_THREAD_PROBE(stop, this);
  this->exit(false);
  delete this;
}

Copy the code

This ->entry_point()(this,this); This entrypoint should be familiar because we mentioned earlier that the first argument passed in the ::JavaThread method represents the name of the function that is called when the thread starts. If you haven’t gotten carsick yet, you’ll remember the code we saw in the jvm.cpp file that creates native_thread=newJavaThread(&thread_entry,sz); We passed a threadentry function, so we found this function defined in jvm.cpp as follows

static void thread_entry(JavaThread* thread, TRAPS) {{HandleMark hm(THREAD);
  Handle obj(THREAD, thread->threadObj());
  JavaValue result(T_VOID);
  JavaCalls::call_virtual(&result,
                          obj,
                          KlassHandle(THREAD, SystemDictionary::Thread_klass()),
                          vmSymbols::run_method_name(), // Notice here
                          vmSymbols::void_method_signature(),
                          THREAD);
}
Copy the code

As you can see, the vmSymbols::run_method_name() call is a callback to the Run method defined in the Java thread. Run_method_name is a macro definition


#define VM_SYMBOLS_DO(template, do_alias)    .template(run_method_name, "run")...Copy the code

So the conclusion is that the Java after it create a thread to invoke the start method can really create a thread, this method will be called a virtual machine running a local thread, local thread creation will call the method that the current system create a thread to create, and thread to be executed when the callback run method of business logic processing

Thread termination method and principle

The termination of a thread can be active or passive. Passive means that the thread exits abnormally or the run method is executed, and the thread terminates automatically. The active way to terminate a Thread is thread.stop (). However, stop() is an expired method and is officially not recommended for the simple reason that it does not guarantee that the Thread will release resources properly, i.e. it does not give the Thread a chance to complete the release. On Linux, we use kill -9 to forcibly terminate a process.

So how do you safely terminate a thread?

Let’s take a look at the following code, which demonstrates a proper way to terminate a thread. We’ll look at how this works later


public class InterruptedDemo implements Runnable{
    @Override
    public void run(a) {
        long i=0l;
        while(! Thread.currentThread().isInterrupted()) {//notice here
            i++;
        }
        System.out.println("result:"+i);
    }
    public static void main(String[] args) throws InterruptedException {
        InterruptedDemo interruptedDemo=new InterruptedDemo(a); Thread thread=new Thread(interruptedDemo);
        thread.start(a); Thread.sleep(1000);// Sleep for a second
        thread.interrupt(a);//notice here}}Copy the code

In the main Thread, interrupt() is invoked, in the run method, and in the while loop thread.currentThread ().isinterrupted () is used to identify a Thread interrupt. So let’s assume that an interrupt flag is maintained in the thread, and thread.interrupt() changes the interrupt flag value so that the while loop in the run method does not hold true and breaks out of the loop, so the thread terminates after the run method completes.

The principle analysis of thread interrupt

What does thread.interrupt() do


public class Thread implements Runnable {.public void interrupt(a) {
        if (this! = Thread.currentThread())
            checkAccess(a);synchronized (blockerLock) {
            Interruptible b = blocker;
            if(b ! = null) {interrupt0(a);// Just to set the interrupt flag
                b.interrupt(this);
                return; }}interrupt0();
    }
...

Copy the code

In this method, interrupt0() is called. This method is native, and we will not repeat the code here. Again, find the JVM_Interrupt definition in the jVm. CPP file

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

Copy the code

This method is relatively simple. It calls thread: : Interrupt (THR) directly, as defined in thread.cpp

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

Copy the code

The Thread:: Interrupt method calls the OS :: Interrupt method. This method is implemented in the os_*.cpp file, where the asterisks represent different platforms. Since the JVM is cross-platform, for each platform, Threads are scheduled differently. Take the OS_linux.cpp file as an example

void os::interrupt(Thread* thread) {
  assert(Thread::current() == thread || Threads_lock->owned_by_self(),
    "possibility of dangling Thread pointer");
  // Get the local thread object
  OSThread* osthread = thread->osthread(a);if(! osthread->interrupted()) {// Determine whether the local thread object is interrupted
    osthread->set_interrupted(true);// Set the interrupt status to true
    // More than one thread can get here with the same value of osthread,
    // resulting in multiple notifications. We do, however, want the store
    // to interrupted() to be visible to other threads before we execute unpark().
    // Here is the memory barrier, which will be dissected in a later article; The purpose of the memory barrier is to make the interrupted state immediately visible to other threads
    OrderAccess::fence(a);//_SleepEvent Is equivalent to thread. sleep, indicating that the Thread is awakened with unpark if it has called sleep
    ParkEvent * const slp = thread->_SleepEvent ;
    if(slp ! =NULL) slp->unpark() ;
  }
  // For JSR166. Unpark even if interrupt status already was set
  if (thread->is_Java_thread())
    ((JavaThread*)thread)->parker() - >unpark(a);//_ParkEvent is used with synchronized blocks and object.wait (), which is equivalent to waking up with unpark
  ParkEvent * ev = thread->_ParkEvent ;
  if(ev ! =NULL) ev->unpark() ;
}

Copy the code

As you can see from the above code analysis, thread.interrupt() actually sets the status of a thread to true and wakes it up with the unpark method of ParkEvent.

  1. For a thread blocked by synchronized, it will continue to try to acquire the lock after being awakened. If it fails, it may still be park
  2. Before calling the Park method of ParkEvent, the interrupt status of the thread is determined and, if true, the interrupt flag of the current thread is cleared
  3. Object.wait, Thread.sleep, and Thread.join throw InterruptedException

Object.wait, Thread.sleep, and Thread.join all throw InterruptedException. First, this exception means that a block has been interrupted by another thread. After being woken up, threads such as Object.wait and thread. sleep will use is_interrupted to check whether the status of the interrupt flag has changed. If true, the interrupt flag will be cleared. InterruptedException is then thrown

If InterruptedException is thrown, it does not mean that the thread must terminate. Instead, it alerts the current thread that an interrupted operation occurred. What happens next depends on the thread

  1. The exception is caught without any processing
  2. Throw an exception out
  3. Stops the current thread and prints an exception message

Thread.sleep () ¶ Thread.sleep () ¶ Thread.sleep () ¶

Find the is_interrupted() method, which is implemented on Linux platforms in the os_linux.cpp file with the following code


bool os::is_interrupted(Thread* thread, bool clear_interrupted) {
  assert(Thread::current() == thread || Threads_lock->owned_by_self(),
    "possibility of dangling Thread pointer");
  OSThread* osthread = thread->osthread(a);bool interrupted = osthread->interrupted(a);// Get the thread interrupt identifier
  if (interrupted && clear_interrupted) {// If the interrupt identifier is true
    osthread->set_interrupted(false);// Set the interrupt flag to false
    // consider thread->_SleepEvent->reset() ... optional optimization
  }
  return interrupted;
}

Copy the code

Thread.sleep () ¶ Thread.sleep () ¶ If you looked carefully, you should be able to quickly find the code in the jvm.cpp file

JVM_ENTRY(void.JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
  JVMWrapper("JVM_Sleep");
  if (millis < 0) {
    THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
  }
  // Determine and clear the thread interrupt status, if the interrupt status is true, throw interrupt exception
  if (Thread::is_interrupted (THREAD, true) && !HAS_PENDING_EXCEPTION) {
    THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
  }
  // Save current thread state and restore it at the end of this block.
  // And set new thread state to SLEEPING.
  JavaThreadSleepState jtss(thread); .Copy the code

Note the code with the Chinese comment above to check is_interrupted status and throw an InterruptedException. At this point, we have analyzed the entire flow of the interruption.

Interrupt identification of Java threads

Now that you know what thread.interrupt does, go back to The Code thread.CurrentThread ().isinterrupted () in Java and you will understand. After setting an interrupt flag to true, isInterrupted() returns true and thus fails to meet the criteria for the while loop. It is important to note that the thread interrupt identifier can be reset in two ways. The first is the aforementioned InterruptedException. The other is to reset the interrupt flag of the current Thread via thread.interrupted ().