Interviewer: How did the Java thread start?

Absrtact: Java thread creation and startup are very simple, but if you ask how a thread starts, you often don't know, or even why you call start() instead of run() at startup?

This article is shared from Huawei cloud community< Thread.start(), working for so long, I don't know how it makes the thread run! >, author: Xiao Fu Ge.

Interviewer: I'll give you a question to see if you have a good chance of entering a big factory. Um... How do Java threads start?

Xie: How did it start? Start started!

Interviewer: anything else?

Xie: Well... No!

Interviewer: Well, this question may or may not make your code how good and good, but the depth and breadth of your technology stack determine whether there is a broad road in your programming career. Still have to work harder!

1, Thread startup analysis

new Thread(() -> {
    // todo
}).start();

Cough, the creation and startup of Java threads are very simple, but if you ask how a thread starts, you often don't know, or even why you call the start() method instead of the run() method?

Well, in order to let everyone have a more intuitive cognition, let's first stand in the perspective of God. This section of Java thread code, the use of JDK method and the corresponding processing process of JVM are shown to facilitate our subsequent step-by-step analysis.

The above is the overall process analysis of thread startup, which involves the following knowledge points:

  • The start of thread will involve the call of local method (JNI), that is, the code written in C + +.
  • In the implementation of JVM, there will be unified processing of threads by different operating systems, such as Win, Linux and Unix.
  • Thread startup will involve the thread's life cycle state (RUNNABLE) and wake-up operation, so there will be callback operation in the end. That is, call our run() method

Next, we will start to analyze the execution content of each step of the source code step by step to understand the thread startup process.

2, Thread start process

1. Thread start UML diagram

Figure 19-2 shows the sequence diagram of the thread startup process. The overall link is long, which will involve the operation of the JVM. The core source code is as follows:

  1. Thread.c
  2. jvm.cpp
  3. thread.cpp
  4. os.cpp
  5. os_linux.cpp
  6. os_windows.cpp
  7. vmSymbols.hpp

2. Java level Thread startup

2.1 start() method

new Thread(() -> {
    // todo
}).start();

// JDK source code
public synchronized void start() {

    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    group.add(this);
    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {}
    }
}
  • The core content of the thread start method start() has been described in its English annotation. Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread. In fact, this is a JVM callback process, which will be discussed in the source code analysis below
  • In addition, start() is a synchronized method, but in order to avoid multiple calls, it will be judged by the thread state in the method. threadStatus != 0
  • group.add(this) is to add the current thread to the thread group, ThreadGroup.
  • start0(), a local method, is called and executed through JNI. This step is the core step of starting the thread.

2.2 start0() local method

// Local method start0
private native void start0();

// Register local methods
public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }
    // ...
}    
  • start0(), a local method, is used to start the thread.
  • registerNatives(), which is used to register some local methods required during thread execution, such as start0, isAlive, yield, sleep, interrupt0, etc.

registerNatives. The local method is defined in Thread.c. The following is the core source code of the definition:

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},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};
  • As you can see from the definition, the start0 method executes the & JVM_ The startthread method starts the thread at the JVM level.

3. JVM create thread

3.1 JVM_StartThread

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;
 
  // Create thread
  native_thread = new JavaThread(&thread_entry, sz);
  // Start thread
  Thread::start(native_thread);

JVM_END
  • There are a lot of codes in this part, but the core content is to create and start threads, and thread_ Entry is also a method, as follows:

thread_entry, thread entry

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(),
                          vmSymbols::void_method_signature(),
                          THREAD);
}

The key point is that when creating a method for introducing this thread entry, thread_ The Java callback function javacalls:: call is included in the entry_ virtual. This callback function will be called by the JVM.

vmSymbols::run_method_name() is the callback method. The source code is as follows:

#define VM_SYMBOLS_DO(template, do_alias)
template(run_method_name, "run") 
  • This run is the run method that will be called in our Java program. Next, we continue to follow the code execution link to find out when the callback method was called.

3.2 JavaThread

native_thread = new JavaThread(&thread_entry, sz);

Next, let's continue to look at the source code execution content of java thread.

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
  Thread()
#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();
  _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);
}
  • ThreadFunction entry_point is the thread above us_ Entry method.
  • size_t stack_sz, indicating the number of existing threads in the process.
  • These two parameters are passed to os::create_thread method, which is used to create a thread.

3.3 os::create_thread

Source code:

  • os_linux.cpp:
  • os_windows.cpp:

As we all know, what is a JVM!, Therefore, its OS service implementation, such as Linux and Windows, will implement the thread creation logic. This is a bit like adapter mode

os_linux -> os::create_thread

bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
  assert(thread->osthread() == NULL, "caller responsible");

  // Allocate the OSThread object
  OSThread* osthread = new OSThread(NULL, NULL);
  // Initial state is ALLOCATED but not INITIALIZED
  osthread->set_state(ALLOCATED);
 
  pthread_t tid;
  int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);

  return true;
}
  • osthread->set_ State (allocated) initializes the allocated state, but it is not initialized at this time.
  • pthread_create is a function that creates threads for Unix like operating systems (Unix, Linux, Mac OS X, etc.).
  • java_start, which focuses on classes, is the method to actually create threads.

3.4 java_start

static void *java_start(Thread *thread) {

  // Thread ID
  int pid = os::current_process_id();

  // Set thread
  ThreadLocalStorage::set_thread(thread);

  // Set thread status: INITIALIZED initialization completed
  osthread->set_state(INITIALIZED);
 
  // Wake up all threads
  sync->notify_all();

 // Loop. If the initialization state is, wait for wait consistently
 while (osthread->get_state() == INITIALIZED) {
    sync->wait(Mutex::_no_safepoint_check_flag);
 }

  // After waiting for wake-up, execute the run method
  thread->run();

  return 0;
}
  • The JVM sets the thread state, and the INITIALIZED initialization is completed.
  • sync->notify_ All(), wake up all threads.
  • osthread->get_ State() = = initialized, while loop waiting
  • Thread - > run() is to wait for the thread to wake up, that is, to execute after the state changes. This is also reflected in our thread execution UML diagram

4. JVM startup thread

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;
 
  // Create thread
  native_thread = new JavaThread(&thread_entry, sz);
  // Start thread
  Thread::start(native_thread);

JVM_END
  • JVM_ There are two steps in startthread: create (new JavaThread) and start (Thread::start). After the process of creation, let's start.

4.1 Thread::start

void Thread::start(Thread* thread) {
  trace("start", thread);

  if (!DisableStartThread) {
    if (thread->is_Java_thread()) {
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    // Different OS will have different startup code logic
    os::start_thread(thread);
  }
}
  • If the thread DisableStartThread is not disabled and is a java thread - > is_ Java_ Thread(), then set the thread status to RUNNABLE.
  • os::start_thread(thread), calling the thread startup method. Different OS will have different startup code logic

4.2 os::start_thread(thread)

void os::start_thread(Thread* thread) {
  // guard suspend/resume
  MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);
  OSThread* osthread = thread->osthread();
  osthread->set_state(RUNNABLE);
  pd_start_thread(thread);
}
  • osthread->set_ State (RUNNABLE) to set the thread state
  • pd_start_thread(thread) starts the thread. Each OS implementation class implements the startup method of its own system. For example, the code of windows system and Linux system is completely different.

4.3 pd_start_thread(thread)

void os::pd_start_thread(Thread* thread) {
  OSThread * osthread = thread->osthread();
  assert(osthread->get_state() != INITIALIZED, "just checking");
  Monitor* sync_with_child = osthread->startThread_lock();
  MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
  sync_with_child->notify();
}
  • This part of the code, notify(), is the most critical. It can wake up the thread.
  • After the thread wakes up, thread - > run() in 3.4; You can continue.

5. JVM thread callback

5.1 thread->run()[JavaThread::run()]

// The first routine called by a new Java thread
void JavaThread::run() {
  // ... initialize thread operation
 
  thread_main_inner();
}
  • os_ Java in linux.cpp class_ Thread - > run() in start finally calls the JavaThread::run() method of thread.cpp.
  • This part needs to continue to look down, thread_main_inner(); method.

5.2 thread_main_inner

void JavaThread::thread_main_inner() {

  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;
}
  • Here is the thread name you are familiar with, this - > set_ native_ thread_ name(this->get_thread_name()).
  • this->entry_ Point(), which actually calls thread in 3.1_ Entry method.
  • thread_ The entry method will eventually be called to javacalls:: call_ Vmsymbols:: run in virtual_ method_ name(). That is, the run() method. At this point, the thread is started and completed. Finally come back!

3, Summary

  • The thread startup process involves the participation of the JVM, so it is really difficult to understand it so thoroughly from a local method without careful understanding.
  • The whole source code analysis can be learned in combination with code calling UML sequence diagram. The basic core processes include: Java thread creation and startup, calling local method start0(), JVM in JVM_ Create and start the startthread, set the thread status and wait to be awakened, start the thread according to different OS and wake up, and finally call back the run() method to start the Java thread.

 

Click focus to learn about Huawei cloud's new technologies for the first time~

Posted on Wed, 01 Dec 2021 08:12:27 -0500 by Sakujou