手把手教你解決Java執行緒中的那些事

green_day發表於2017-11-28

#引言

說到Thread大家都很熟悉,我們平常寫併發程式碼的時候都會接觸到,那麼我們來看看下面這段程式碼是如何初始化以及執行的呢?

public class ThreadDemo {

    public static void main(String[] args) {
        new Thread().start();
    }
}複製程式碼

#初始化流程

程式碼就一行很簡單,那麼這行簡單的程式碼背後做了那些事情呢?

初始化Thread這個類

首先JVM會去載入Thread的位元組碼,初始化這個類,這裡即呼叫下面這段程式碼:

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

是個native方法,那麼我們去看看內部實現是什麼,具體的目錄是openjdk/jdk/src/share/native/java/lang/Thread.c,

//JVM字首開頭的方法,具體實現在JVM中
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 ")[[" STE, (void *)&JVM_DumpThreads},
    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};

#undef THD
#undef OBJ
#undef STE
#undef STR

//jclass cls即為java.lang.Thread
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}複製程式碼

可以發現具體的實現都是由這些JVM開頭的方法決定的,而這幾個方法的具體實現都在hotspot\src\share\vm\prims\jvm.cpp檔案中,而RegisterNatives我目前的理解其實類似一個方法表,從Java方法到native方法的一個對映,具體的原理後面再研究。

獲取更多視訊資料加群:554355695

如果你想學習Java工程化、高效能及分散式、高效能、深入淺出。效能調優、Spring,MyBatis,Netty原始碼分析和大資料等知識點可以來找我。

而現在我就有一個平臺可以提供給你們學習,讓你在實踐中積累經驗掌握原理。主要方向是JAVA架構師。如果你想拿高薪,想突破瓶頸,想跟別人競爭能取得優勢的,想進BAT但是有擔心面試不過的,可以加我的Java架構進階群:554355695

注:加群要求
1、具有2-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。 
2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。 
3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。 
4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。 
5.阿里Java高階大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知! 
6.小號加群一律不給過,謝謝。複製程式碼

#初始化Thread物件

其實就是一些賦值,名字、執行緒ID這些,這兩個變數都是static,用synchronized修飾,保證執行緒安全性。

  public Thread() {
        //nextThreadNum就是變數的自增,用synchronized修飾保證可見性
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

        private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }

        private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        // 安全相關的一坨東西....

        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }


    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }複製程式碼

建立並啟動執行緒

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) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}複製程式碼

這裡start0()是個native方法,對應jvm.cpp中的JVM_StartThread,我們看到很多方法都是JVM_ENTRY開頭,JVM_END結尾,類似於{}的作用,這裡是將很多公共的操作封裝到了JVM_ENTRY裡面.

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;

  // We cannot hold the Threads_lock when we throw an exception,
  // due to rank ordering issues. Example:  we might need to grab the
  // Heap_lock while we construct the exception.
  bool throw_illegal_thread_state = false;

  // We must release the Threads_lock before we can post a jvmti event
  // in Thread::start.
  {
    // 加鎖
    MutexLocker mu(Threads_lock);

    // 自從JDK 5之後 java.lang.Thread#threadStatus可以用來阻止重啟一個已經啟動
    // 的執行緒,所以這裡的JavaThread通常為空。然而對於一個和JNI關聯的執行緒來說,線上程
    // 被建立和更新他的threadStatus之前會有一個小視窗,因此必須檢查這種情況
    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
    } else {
      // We could also check the stillborn flag to see if this thread was already stopped, but
      // for historical reasons we let the thread detect that itself when it starts running

      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
      // 為C++執行緒結構體分配記憶體並建立native執行緒。從java取出的stack size是有符號的,因此這裡
      // 需要進行一次轉換,避免傳入負數導致建立一個非常大的棧。
      size_t sz = size > 0 ? (size_t) size : 0;
      native_thread = new JavaThread(&thread_entry, sz);

      // At this point it may be possible that no osthread was created for the
      // JavaThread due to lack of memory. Check for this situation and throw
      // an exception if necessary. Eventually we may want to change this so
      // that we only grab the lock if the thread was created successfully -
      // then we can also do this check and throw the exception in the
      // JavaThread constructor.
      if (native_thread->osthread() != NULL) {
        // Note: the current thread is not being used within "prepare".
        native_thread->prepare(jthread);
      }
    }
  }

  if (throw_illegal_thread_state) {
    THROW(vmSymbols::java_lang_IllegalThreadStateException());
  }

  assert(native_thread != NULL, "Starting null thread?");

  if (native_thread->osthread() == NULL) {
    // No one should hold a reference to the 'native_thread'.
    delete native_thread;
    if (JvmtiExport::should_post_resource_exhausted()) {
      JvmtiExport::post_resource_exhausted(
        JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
        "unable to create new native thread");
    }
    THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
              "unable to create new native thread");
  }

  Thread::start(native_thread);

JVM_END複製程式碼

基本上這裡就是先加鎖,做些檢查,然後建立JavaThread,如果建立成功的話會呼叫prepare(),然後是一些異常處理,沒有異常的話最後會啟動執行緒,那麼下面我們先來看看JavaThread是如何被建立的。

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();//這個方法其實就是一堆變數的初始化,不是Null就是0.
  _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;
  // 根據傳進來的entry_point判斷要建立的執行緒的型別。
  thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
                                                     os::java_thread;
  os::create_thread(this, thr_type, stack_sz);
  // _osthread可能是Null,因此我們耗盡了記憶體(太多的活躍執行緒)。我們需要丟擲OOM,然而不能在這做,因為呼叫者可能
  // 還持有鎖,而所有的鎖都必須在丟擲異常之前被釋放。
  // 程式碼執行到這,執行緒還是suspended狀態,因為執行緒必須被建立者直接啟動。
}

void JavaThread::initialize() {
  // Initialize fields
  // ...
  set_thread_state(_thread_new); // 執行緒的初始狀態
 // ...

}複製程式碼

JavaThreadState記錄了執行緒記錄了執行緒正在執行的程式碼在哪一部分,這個資訊可能會被安全點使用到(GC),最核心的有四種:

  • _thread_new 剛開始啟動,但還沒執行初始化程式碼,更可能還在OS初始化的層面
  • _thread_in_native 在native程式碼中
  • _thread_in_vm 在vm中執行
  • _thread_in_Java 執行在解釋或者編譯後的Java程式碼中

每個狀態都會對應一箇中間的轉換狀態,這些額外的中間狀態使得安全點的程式碼能夠更快的處理某一執行緒狀態而不用掛起執行緒。

enum JavaThreadState {
  _thread_uninitialized     =  0, // should never happen (missing initialization)
  _thread_new               =  2, // just starting up, i.e., in process of being initialized
  _thread_new_trans         =  3, // corresponding transition state (not used, included for completness)
  _thread_in_native         =  4, // running in native code
  _thread_in_native_trans   =  5, // corresponding transition state
  _thread_in_vm             =  6, // running in VM
  _thread_in_vm_trans       =  7, // corresponding transition state
  _thread_in_Java           =  8, // running in Java or in stub code
  _thread_in_Java_trans     =  9, // corresponding transition state (not used, included for completness)
  _thread_blocked           = 10, // blocked in vm
  _thread_blocked_trans     = 11, // corresponding transition state
  _thread_max_state         = 12  // maximum thread state+1 - used for statistics allocation
};複製程式碼

我們看到os::create_thread(this, thr_type, stack_sz);這行程式碼會去實際的建立執行緒,首先我們知道Java宣傳的是一次編譯,到處執行,那麼究竟是怎麼做到在不同的CPU、作業系統上還能夠保持良好的可移植性呢?

// 平臺相關的東東
#ifdef TARGET_OS_FAMILY_linux
# include "os_linux.hpp"
# include "os_posix.hpp"
#endif
#ifdef TARGET_OS_FAMILY_solaris
# include "os_solaris.hpp"
# include "os_posix.hpp"
#endif
#ifdef TARGET_OS_FAMILY_windows
# include "os_windows.hpp"
#endif
#ifdef TARGET_OS_FAMILY_aix
# include "os_aix.hpp"
# include "os_posix.hpp"
#endif
#ifdef TARGET_OS_FAMILY_bsd
# include "os_posix.hpp"
# include "os_bsd.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_x86
# include "os_linux_x86.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_sparc
# include "os_linux_sparc.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_zero
# include "os_linux_zero.hpp"
#endif
#ifdef TARGET_OS_ARCH_solaris_x86
# include "os_solaris_x86.hpp"
#endif
#ifdef TARGET_OS_ARCH_solaris_sparc
# include "os_solaris_sparc.hpp"
#endif
#ifdef TARGET_OS_ARCH_windows_x86
# include "os_windows_x86.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_arm
# include "os_linux_arm.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_ppc
# include "os_linux_ppc.hpp"
#endif
#ifdef TARGET_OS_ARCH_aix_ppc
# include "os_aix_ppc.hpp"
#endif
#ifdef TARGET_OS_ARCH_bsd_x86
# include "os_bsd_x86.hpp"
#endif
#ifdef TARGET_OS_ARCH_bsd_zero
# include "os_bsd_zero.hpp"
#endif複製程式碼

我們看到os.hpp中有這樣一段程式碼,能夠根據不同的作業系統選擇include不同的標頭檔案,從而將平臺相關的邏輯封裝到對應的庫檔案中,我們這裡以linux為例,create_thread最終會呼叫os_linux.cpp中的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);
  if (osthread == NULL) {
    return false;
  }

  // set the correct thread state
  osthread->set_thread_type(thr_type);

  // 初始狀態為ALLOCATED,而不是INITIALIZED
  osthread->set_state(ALLOCATED);

  thread->set_osthread(osthread);

  // init thread attributes
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

  // stack size
  if (os::Linux::supports_variable_stack_size()) {
    // 如果上層未傳遞則計算stack_size
    if (stack_size == 0) {
      //如果為compiler_thread,則分配4M,否則預設會分配1M
      stack_size = os::Linux::default_stack_size(thr_type);

      switch (thr_type) {
      case os::java_thread:
        // Java執行緒用ThreadStackSize,這個值可以通過-Xss指定
        assert (JavaThread::stack_size_at_create() > 0, "this should be set");
        stack_size = JavaThread::stack_size_at_create();
        break;
      case os::compiler_thread:
        if (CompilerThreadStackSize > 0) {
          stack_size = (size_t)(CompilerThreadStackSize * K);
          break;
        } // else fall through:
          // use VMThreadStackSize if CompilerThreadStackSize is not defined
      case os::vm_thread:
      case os::pgc_thread:
      case os::cgc_thread:
      case os::watcher_thread:
        if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K);
        break;
      }
    }

    // 用兩者較大的那個,min_stack_allowed預設為128K
    stack_size = MAX2(stack_size, os::Linux::min_stack_allowed);
    pthread_attr_setstacksize(&attr, stack_size);
  } else {
    // let pthread_create() pick the default value.
  }

  // glibc guard page
  pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));

  ThreadState state;

  {
    // 檢查是否需要加鎖
    bool lock = os::Linux::is_LinuxThreads() && !os::Linux::is_floating_stack();
    if (lock) {
      os::Linux::createThread_lock()->lock_without_safepoint_check();
    }

    pthread_t tid;
    // Linux用於建立執行緒的函式,這個執行緒通過執行java_start來啟動,其中thread是作為java_start的引數傳遞進來的
    // 具體可見手冊:http://man7.org/linux/man-pages/man3/pthread_create.3.html
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);

    pthread_attr_destroy(&attr);

    if (ret != 0) {
      // 建立失敗,將_osthread置為空,還記得在jvm.cpp的JVM_StartThread中會根據_osthread是否為空來判斷
      // 是否建立成功
      if (PrintMiscellaneous && (Verbose || WizardMode)) {
        perror("pthread_create()");
      }
      // 清理資源,並解鎖
      thread->set_osthread(NULL);
      delete osthread;
      if (lock) os::Linux::createThread_lock()->unlock();
      return false;
    }

    // 建立成功會將底層執行緒的ID儲存在tid中
    osthread->set_pthread_id(tid);

    // 等待子執行緒建立完成或者終止
    {
      Monitor* sync_with_child = osthread->startThread_lock();
      MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
      while ((state = osthread->get_state()) == ALLOCATED) {
        sync_with_child->wait(Mutex::_no_safepoint_check_flag);
      }
    }

    if (lock) {
      os::Linux::createThread_lock()->unlock();
    }
  }

  // 執行緒的數目達到極限了
  if (state == ZOMBIE) {
      thread->set_osthread(NULL);
      delete osthread;
      return false;
  }

  // The thread is returned suspended (in state INITIALIZED),
  // and is started higher up in the call chain
  assert(state == INITIALIZED, "race condition");
  return true;
}複製程式碼

下面我們來看看pthread_create會執行的回撥函式java_start,這個方法是所有新建立的執行緒必走的流程。

static void *java_start(Thread *thread) {
  // 嘗試隨機化熱棧幀快取記憶體行的索引,這有助於優化擁有相同棧幀執行緒去互相驅逐彼此的快取行時,執行緒
  // 可以是同一個JVM例項或者不同的JVM例項,這尤其有助於擁有超執行緒技術的處理器。
  static int counter = 0;
  int pid = os::current_process_id();
  alloca(((pid ^ counter++) & 7) * 128);

  ThreadLocalStorage::set_thread(thread);

  OSThread* osthread = thread->osthread();
  Monitor* sync = osthread->startThread_lock();

  // non floating stack LinuxThreads needs extra check, see above
  if (!_thread_safety_check(thread)) {
    // notify parent thread
    MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
    osthread->set_state(ZOMBIE);
    sync->notify_all();
    return NULL;
  }

  // thread_id is kernel thread id (similar to Solaris LWP id)
  osthread->set_thread_id(os::Linux::gettid());

  if (UseNUMA) {
    int lgrp_id = os::numa_get_group_id();
    if (lgrp_id != -1) {
      thread->set_lgrp_id(lgrp_id);
    }
  }
  // initialize signal mask for this thread
  os::Linux::hotspot_sigmask(thread);

  // initialize floating point control register
  os::Linux::init_thread_fpu_state();

  // handshaking with parent thread
  {
    MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);

    // 設定為已經初始化完成,並notify父執行緒
    osthread->set_state(INITIALIZED);
    sync->notify_all();

    // wait until os::start_thread()
    while (osthread->get_state() == INITIALIZED) {
      sync->wait(Mutex::_no_safepoint_check_flag);
    }
  }


  //這裡上層傳遞過來的是JavaThread,因此會呼叫JavaThread#run()方法  
  thread->run();

  return 0;
}


void JavaThread::run() {
  // 初始化本地執行緒分配快取(TLAB)相關的屬性
  this->initialize_tlab();

  // used to test validitity of stack trace backs
  this->record_base_of_stack_pointer();

  // Record real stack base and size.
  this->record_stack_base_and_size();

  // Initialize thread local storage; set before calling MutexLocker
  this->initialize_thread_local_storage();

  this->create_stack_guard_pages();

  this->cache_global_variables();

  // 將執行緒的狀態更改為_thread_in_vm,執行緒已經可以被VM中的安全點相關的程式碼處理了,也就是說必須
  // JVM如果執行緒在執行native裡面的程式碼,是搞不了安全點的,待確認
  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();
  }

  // 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();

  // Note, thread is no longer valid at this point!
}

void JavaThread::thread_main_inner() {
  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);
    // 這個entry_point就是JVM_StartThread中傳遞過來的那個,也就是thread_entry
    this->entry_point()(this, this);
  }

  DTRACE_THREAD_PROBE(stop, this);

  this->exit(false);
  delete this;
}複製程式碼

我們最後再看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);
}
vmSymbols,這個是JVM對於那些需要特殊處理的類、方法等的宣告,我的理解就是一個方法表,根據下面這行程式碼可以看出來,其實呼叫的就是run()方法.

  /* common method and field names */                                                            
  template(run_method_name,                           "run")複製程式碼

然後我們回到JVM_StartThread方法中,這裡會接著呼叫prepare()方法,設定執行緒優先順序(將Java中的優先順序對映到os中),然後新增到執行緒佇列中去.最後會呼叫Thread::start(native_thread);
啟動執行緒。

void Thread::start(Thread* thread) {
  trace("start", thread);
  // start和resume不一樣,start被synchronized修飾
  if (!DisableStartThread) {
    if (thread->is_Java_thread()) {
     // 在啟動執行緒之前初始化執行緒的狀態為RUNNABLE,為啥不能在之後設定呢?因為啟動之後可能是
     //  等待或者睡眠等其他狀態,具體是什麼我們不知道
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    os::start_thread(thread);
  }
}複製程式碼

#總結
一個Java執行緒對應一個JavaThread->OSThread -> Native Thread
在呼叫java.lang.Thread#start()方法之前不會啟動執行緒,僅僅呼叫run()方法只是會在當前執行緒執行而已
//todo

相關文章