原來還能這樣看Java執行緒的狀態及轉換

小牛呼嚕嚕發表於2023-03-27

作者:小牛呼嚕嚕 | https://xiaoniuhululu.com
計算機內功、JAVA底層、面試、職業成長相關資料等更多精彩文章在公眾號「小牛呼嚕嚕

大家好,我是呼嚕嚕,最近一直在梳理Java併發,但內容雜且偏晦澀,今天我們一起來聊聊Java 執行緒的狀態及轉換 先來夯實一下基礎,萬丈高樓平地起,路還是得慢慢走。

Java執行緒的生命週期

我們先來看下Java執行緒的生命週期圖:

上圖也是本文的大綱,我們下面依次聊聊java各個執行緒狀態及其他們的轉換。

執行緒初始狀態

執行緒初始狀態(NEW): 當前執行緒處於執行緒被建立出來但沒有被呼叫start()

在Java執行緒的時間中,關於執行緒的一切的起點是從Thread 類的物件的建立開始,一般實現Runnable介面 或者 繼承Thread類的類,例項化一個物件出來,執行緒就進入了初始狀態

Thread thread = new Thread()

由於執行緒在我們作業系統中也是非常寶貴的資源,在實際開發中,我們常常用執行緒池來重複利用現有的執行緒來執行任務,避免多次建立和銷燬執行緒,從而降低建立和銷燬執行緒過程中的代價。Java 給我們提供了 Executor 介面來使用執行緒池,檢視其JDK1.8原始碼,發現其內部封裝了Thread t = new Thread()

public class Executors {
    ...
  static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        ...

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
    ...
}

在thread類原始碼中,我們還能發現執行緒狀態的列舉類State

    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        RUNNABLE,

        BLOCKED,

        WAITING,

        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

所謂執行緒的狀態,在java原始碼中都是透過threadStatus的值來表示的

   /* Java thread status for tools,
     * initialized to indicate thread 'not yet started'
     */

    private volatile int threadStatus = 0;

StatethreadStatus 透過toThreadState方法對映轉換

    public State getState() {
        // get current thread state
        return sun.misc.VM.toThreadState(threadStatus);
    }

//--- --- ---

    public static State toThreadState(int var0) {
        if ((var0 & 4) != 0) {
            return State.RUNNABLE;
        } else if ((var0 & 1024) != 0) {
            return State.BLOCKED;
        } else if ((var0 & 16) != 0) {
            return State.WAITING;
        } else if ((var0 & 32) != 0) {
            return State.TIMED_WAITING;
        } else if ((var0 & 2) != 0) {
            return State.TERMINATED;
        } else {
            return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
        }
    }

到這裡我們就可以發現,Thread t = new Thread()在Java中只是設定了執行緒的狀態,作業系統中並沒有的實際執行緒的建立

執行緒執行狀態

執行緒執行狀態(RUNNABLE),執行緒被呼叫了start()等待執行的狀態

在Linux作業系統層面,包含RunningReady 狀態。其中Ready狀態是等待 CPU 時間片。現今主流的JVM,比如hotspot虛擬機器都是把Java 執行緒,對映到作業系統OS底層的執行緒上,把排程委託給了作業系統。而作業系統比如Linux,它是多工作業系統,充分利用CPU的高效能,將CPU的時間分片,讓單個CPU實現"同時執行"多工的效果。

更多精彩文章在公眾號「小牛呼嚕嚕

Linux的任務排程又採用搶佔式輪轉排程,我們不考慮特權程式的話OS會選擇在CPU上佔用的時間最少程式,優先在cpu上分配資源,其對應的執行緒去執行任務,儘可能地維護任務排程公平。RunningReady 狀態的執行緒在CPU中切換狀態非常短暫。大概只有 0.01 秒這一量級,區分開來意義不大,java將這2個狀態統一用RUNNABLE來表示

thread.start()原始碼解析

我們接下來看看為什麼說執行thread.start()後,執行緒的才"真正的建立"

public class ThreadTest {
    /**
     * 繼承Thread類
     */
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("This is child thread");
        }
    }
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

其中thread.start()方法的原始碼中,會去呼叫start0()方法,而start0()private native void start0();JVM呼叫Native方法的話,會進入到不受JVM控制的世界裡

Thread類例項化的同時,會首先呼叫registerNatives方法,註冊本地Native方法,動態繫結JVM方法

private static native void registerNatives();
    static {
        registerNatives();
    }

Thread類中透過registerNatives將指定的本地方法繫結到指定函式,比如start0本地方法繫結到JVM_StartThread函式:

...
static JNINativeMethod methods[] = {
    {"start0",           "()V",        (void *)&JVM_StartThread},
    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},
    ...

原始碼見:http://hg.openjdk.java.net/jdk8u/jdk8u60/jdk/file/935758609767/src/share/native/java/lang/Thread.c

JVM_StartThread 是JVM層函式,拋去各種情況的處理,主要是透過 new JavaThread(&thread_entry, sz)來建立JVM執行緒物件

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

	//表示是否有異常,當丟擲異常時需要獲取Heap_lock。
  bool throw_illegal_thread_state = false;

  // 在釋出jvmti事件之前,必須釋放Threads_lock
  // in Thread::start.
  {
    // 獲取 Threads_lock鎖
    MutexLocker mu(Threads_lock);


    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));
      
        // 建立JVM執行緒(用JavaThread物件表示)
      size_t sz = size > 0 ? (size_t) size : 0;
      native_thread = new JavaThread(&thread_entry, sz);
      ...
    }
  }

  ...

  Thread::start(native_thread);//啟動核心執行緒

JVM_END

原始碼見:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/prims/jvm.cpp

我們再來看看JavaThread的實現,發現內部透過 os::create_thread(this, thr_type, stack_sz);來呼叫不同作業系統的建立執行緒方法建立執行緒。

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);//呼叫不同作業系統的建立執行緒方法建立執行緒

}

原始碼見:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/runtime/thread.cpp

我們都知道Java是跨平臺的,但是native各種方法底層c/c++程式碼對各平臺都需要有對應的相容,我們這邊以linux為例,其他平臺就大家自行去查閱了

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

  // Initial state is ALLOCATED but not 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()) {
    // calculate stack size if it's not specified by caller
    if (stack_size == 0) {
      stack_size = os::Linux::default_stack_size(thr_type);

      switch (thr_type) {
      case os::java_thread:
        // Java threads use ThreadStackSize which default value can be
        // changed with the flag -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;
      }
    }

    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;

  {
    // Serialize thread creation if we are running with fixed stack LinuxThreads
    bool lock = os::Linux::is_LinuxThreads() && !os::Linux::is_floating_stack();
    if (lock) {
      os::Linux::createThread_lock()->lock_without_safepoint_check();
    }

    pthread_t tid;
      //透過pthread_create方法建立核心級執行緒 !
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);

    pthread_attr_destroy(&attr);

    if (ret != 0) {
      if (PrintMiscellaneous && (Verbose || WizardMode)) {
        perror("pthread_create()");
      }
      // Need to clean up stuff we've allocated so far
      thread->set_osthread(NULL);
      delete osthread;
      if (lock) os::Linux::createThread_lock()->unlock();
      return false;
    }

    // Store pthread info into the OSThread
    osthread->set_pthread_id(tid);

    // Wait until child thread is either initialized or aborted
    {
      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();
    }
  }

  // Aborted due to thread limit being reached
  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;
}

原始碼見:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/os/linux/vm/os_linux.cpp

主要透過pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread),它是unix 建立執行緒的方法,linux也繼承了。呼叫後在linux系統中會建立一個核心級的執行緒。也就是說這個時候作業系統中執行緒才真正地誕生

更多精彩文章在公眾號「小牛呼嚕嚕

但此時執行緒才誕生,那是怎麼啟動的?我們回到JVM_StartThread原始碼中,Thread::start(native_thread)很明顯這行程式碼就表示啟動native_thread = new JavaThread(&thread_entry, sz)建立的執行緒,我們來繼續看看其原始碼

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()) {
      // 設定執行緒狀態
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    os::start_thread(thread);
  }
}

原始碼:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/runtime/thread.cpp

os::start_thread它封裝了pd_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);
}

當作業系統的執行緒啟動完之後,我們再回到pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread),會去java_start這個執行緒入口函式進行OS核心級執行緒的初始化,並開始啟動JavaThread

// Thread start routine for all newly created threads
static void *java_start(Thread *thread) {
  // Try to randomize the cache line index of hot stack frames.
  // This helps when threads of the same stack traces evict each other's
  // cache lines. The threads can be either from the same JVM instance, or
  // from different JVM instances. The benefit is especially true for
  // processors with hyperthreading technology.
  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 parent thread
    osthread->set_state(INITIALIZED);
    sync->notify_all();

    // 等待,直到作業系統級執行緒全部啟動
    while (osthread->get_state() == INITIALIZED) {
      sync->wait(Mutex::_no_safepoint_check_flag);
    }
  }

  // 開始執行JavaThread::run
  thread->run();

  return 0;
}

原始碼:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/os/linux/vm/os_linux.cpp

thread->run()其實就是JavaThread::run()也表明方法開始回撥,從OS層方法回到JVM層方法
,我們再來看下其實現:

// The first routine called by a new Java thread
void JavaThread::run() {
  // initialize thread-local alloc buffer related fields
  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 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);
  }

  JFR_ONLY(Jfr::on_thread_start(this);)

  // 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);
    this->entry_point()(this, this);//JavaThread物件中傳入的entry_point為Thread物件的Thread::run方法
  }

  DTRACE_THREAD_PROBE(stop, this);

  this->exit(false);
  delete this;
}


原始碼:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/runtime/thread.cpp

由於JavaThread定義可知JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz)中引數entry_point是外部傳入,那我們想想JavaThread是什麼時候例項化的?

沒錯,就是我們一開始的JVM_StartThreadnative_thread = new JavaThread(&thread_entry, sz);
也就是說this->entry_point()(this, this)實際上是回撥的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);
}

原始碼:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/prims/jvm.cpp
透過JavaCalls::call_virtual方法,又從JVM層 回到了Java語言層 ,即MyThread thread = new MyThread(); thread.start();

一切又回到了起點,這就是Javathread.start()內部完整的一個流程,HotSpot虛擬機器實現的Java執行緒其實是對Linux核心級執行緒的直接對映,將Java涉及到的所有執行緒排程、記憶體分配都交由作業系統進行管理

執行緒終止狀態

執行緒終止狀態(TERMINATED),表示該執行緒已經執行完畢。

當一個執行緒執行完畢,或者主執行緒的main()方法完成時,我們就認為它終止了。終止的執行緒無法在被使用,如果呼叫start()方法,會丟擲java.lang.IllegalThreadStateException異常,這一點我們可以從start原始碼中很容易地得到

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    ...
}

執行緒阻塞狀態

執行緒阻塞狀態(BLOCKED),需要等待鎖釋放或者說獲取鎖失敗時,執行緒阻塞

public class BlockedThread implements Runnable {
    @Override
    public void run() {
        synchronized (BlockedThread.class){
            while (true){
                
            }
        }
    }
}

從Thread原始碼的註釋中,我們可以知道等待鎖釋放或者說獲取鎖失敗,主要有下面3中情況:

  1. 進入 synchronized 方法時
  2. 進入 synchronized 塊時
  3. 呼叫 wait 後, 重新進入 synchronized 方法/塊時

其中第三種情況,大家可以先思考一下,我們留在下文執行緒等待狀態再詳細展開

執行緒等待狀態

執行緒等待狀態(WAITING),表示該執行緒需要等待其他執行緒做出一些特定動作(通知或中斷)。

wait/notify/notifyAll

我們緊接著上一小節,呼叫 wait 後, 重新進入synchronized 方法/塊時,我們來看看期間發生了什麼?

執行緒1呼叫物件A的wait方法後,會釋放當前的鎖,然後讓出CPU時間片,執行緒會進入該物件的等待佇列中,執行緒狀態變為 等待狀態WAITING
當另一個執行緒2呼叫了物件A的notify()/notifyAll()方法

notify()方法只會喚醒沉睡的執行緒,不會立即釋放之前佔有的物件A的鎖,必須執行完notify()方法所在的synchronized程式碼塊後才釋放。所以在程式設計中,儘量在使用了notify/notifyAll()後立即退出臨界區

執行緒1收到通知後退出等待佇列,並進入執行緒執行狀態RUNNABLE,等待 CPU 時間片分配, 進而執行後續操作,接著執行緒1重新進入 synchronized 方法/塊時,競爭不到鎖,執行緒狀態變為執行緒阻塞狀態BLOCKED。如果競爭到鎖,就直接接著執行。執行緒等待狀態 切換到執行緒阻塞狀態,無法直接切換,需要經過執行緒執行狀態。

我們再來看一個例子,鞏固鞏固:

public class WaitNotifyTest {
    public static void main(String[] args) {
        Object A = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("執行緒1等待獲取 物件A的鎖...");
                synchronized (A) {
                    try {
                        System.out.println("執行緒1獲取了 物件A的鎖");
                        Thread.sleep(3000);
                        System.out.println("執行緒1開始執行wait()方法進行等待,進入到等待佇列......");
                        A.wait();
                        System.out.println("執行緒1等待結束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("執行緒2等待獲取 物件A的鎖...");
                synchronized (A) {
                    System.out.println("執行緒2獲取了 物件A的鎖");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("執行緒2將要執行notify()方法進行喚醒執行緒1");
                    A.notify();
                }
            }
        }).start();
    }
}

結果:

執行緒1等待獲取 物件A的鎖...
執行緒1獲取了 物件A的鎖
執行緒2等待獲取 物件A的鎖...
執行緒1開始執行wait()方法進行等待,進入到等待佇列......
執行緒2獲取了 物件A的鎖
執行緒2將要執行notify()方法進行喚醒執行緒1
執行緒1等待結束

需要注意的是,wait/notify/notifyAll 只能在synchronized修飾的方法、塊中使用notify 是隻隨機喚醒一個執行緒,而 notifyAll 是喚醒所有等待佇列中的執行緒

join

Thread類中的join方法的主要作用能讓執行緒之間的並行執行變為序列執行,當前執行緒等該加入該執行緒後面,等待該執行緒終止

public static void main(String[] args) {
  Thread thread = new Thread();
  thread.start();
  thread.join();
  ...
}

上面一個例子表示,程式在main主執行緒中呼叫thread執行緒的join方法,意味著main執行緒放棄CPU時間片(主執行緒會變成 WAITING 狀態),並返回thread執行緒,繼續執行直到執行緒thread執行完畢,換句話說在主執行緒執行過程中,插入thread執行緒,還得等thread執行緒執行完後,才輪到主執行緒繼續執行

如果檢視JDKthread.join()底層實現,會發現其實內部封裝了wait(),notifyAll()

park/unpark

LockSupport.park() 掛起當前執行緒;LockSupport.unpark(暫停執行緒物件) 恢復某個執行緒

package com.zj.ideaprojects.demo.test3;

import java.util.concurrent.Executors;
import java.util.concurrent.locks.LockSupport;

public class ThreadLockSupportTest {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("start.....");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("park....");
            LockSupport.park();
            System.out.println("resume.....");

        });
        thread.start();
        Thread.sleep(3000);
        System.out.println("unpark....");
        LockSupport.unpark(thread);

    }
}


結果:

start.....
park....
unpark....
resume.....

當程式呼叫LockSupport.park(),會讓當前執行緒A的執行緒狀態會從 RUNNABLE 變成 WAITING,然後main主執行緒呼叫LockSupport.unpark(thread),讓指定的執行緒即執行緒A,從 WAITING 回到 RUNNABLE 。我們可以發現
park/unparkwait/notify/notifyAll很像,但是他們有以下的區別:

  1. wait,notify 和 notifyAll 必須事先獲取物件鎖,而 unpark 不必
  2. park、unpark 可以先 unpark ,而 wait、notify 不能先 notify,必須先wait
  3. unpark 可以精準喚醒某一個確定的執行緒。而 notify 只能隨機喚醒一個等待執行緒,notifyAll 是喚醒所以等待執行緒,就不那麼精確

超時等待狀態

超時等待狀態(TIMED_WAITING),也叫限期等待,可以在指定的時間後自行返回而不是像 WAITING 那樣一直等待。

這部分比較簡單,它和執行緒等待狀態(WAITING)狀態 非常相似,區別就是方法的引數舒服傳入限制時間,在 Timed Waiting狀態時會等待超時,之後由系統喚醒,或者也可以提前被通知喚醒如 notify

相關方法主要有:

1. Object.wait(long)
2. Thread.join(long) 
3. LockSupport.parkNanos(long)
4. LockSupport.parkUntil(long)
5. Thread.sleep(long)

需要注意的是Thread.sleep(long),當執行緒執行sleep方法時,不會釋放當前的鎖(如果當前執行緒進入了同步鎖),也不會讓出CPU。sleep(long)可以用指定時間使它自動喚醒過來,如果時間不到只能呼叫interrupt方法強行打斷。

參考資料:

https://hg.openjdk.java.net/jdk8u

《併發程式設計的藝術》

https://www.jianshu.com/p/216a41352fd8


本篇文章到這裡就結束啦,如果我的文章對你有所幫助,還請幫忙一鍵三連:點贊、關注、收藏,你的支援會激勵我輸出更高質量的文章,感謝!

原文映象:原來還能這樣看Java執行緒的狀態及轉換

計算機內功、原始碼解析、科技故事、專案實戰、面試八股等更多硬核文章,首發於公眾號「小牛呼嚕嚕」,我們下期再見!

相關文章