原來(lái)還能這樣看Java線程的狀態(tài)及轉(zhuǎn)換

大家好,我是小滿,最近一直在梳理Java并發(fā),但內(nèi)容雜且偏晦澀,今天我們一起來(lái)聊聊Java 線程的狀態(tài)及轉(zhuǎn)換 先來(lái)夯實(shí)一下基礎(chǔ),萬(wàn)丈高樓平地起,路還是得慢慢走。

Java線程的生命周期

我們先來(lái)看下Java線程的生命周期圖:


上圖也是本文的大綱,我們下面依次聊聊java各個(gè)線程狀態(tài)及其他們的轉(zhuǎn)換。

線程初始狀態(tài)

線程初始狀態(tài)(NEW): 當(dāng)前線程處于線程被創(chuàng)建出來(lái)但沒(méi)有被調(diào)用start()

在Java線程的時(shí)間中,關(guān)于線程的一切的起點(diǎn)是從Thread 類的對(duì)象的創(chuàng)建開(kāi)始,一般實(shí)現(xiàn)Runnable接口 或者 繼承Thread類的類,實(shí)例化一個(gè)對(duì)象出來(lái),線程就進(jìn)入了初始狀態(tài)

Thread thread = new Thread()


由于線程在我們操作系統(tǒng)中也是非常寶貴的資源,在實(shí)際開(kāi)發(fā)中,我們常常用線程池來(lái)重復(fù)利用現(xiàn)有的線程來(lái)執(zhí)行任務(wù),避免多次創(chuàng)建和銷毀線程,從而降低創(chuàng)建和銷毀線程過(guò)程中的代價(jià)。Java 給我們提供了 Executor 接口來(lái)使用線程池,查看其JDK1.8源碼,發(fā)現(xiàn)其內(nèi)部封裝了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類源碼中,我們還能發(fā)現(xiàn)線程狀態(tài)的枚舉類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;

? ? }

所謂線程的狀態(tài),在java源碼中都是通過(guò)threadStatus的值來(lái)表示的

/* Java thread status for tools,

? ? * initialized to indicate thread 'not yet started'

? ? */

? ? private volatile int threadStatus = 0;

State?和?threadStatus?通過(guò)toThreadState方法映射轉(zhuǎn)換

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;

? ? ? ? }

? ? }

到這里我們就可以發(fā)現(xiàn),Thread t = new Thread()在Java中只是設(shè)置了線程的狀態(tài),操作系統(tǒng)中并沒(méi)有的實(shí)際線程的創(chuàng)建

線程運(yùn)行狀態(tài)

線程運(yùn)行狀態(tài)(RUNNABLE),線程被調(diào)用了start()等待運(yùn)行的狀態(tài)

在Linux操作系統(tǒng)層面,包含Running和?Ready?狀態(tài)。其中Ready狀態(tài)是等待 CPU 時(shí)間片?,F(xiàn)今主流的JVM,比如hotspot虛擬機(jī)都是把Java 線程,映射到操作系統(tǒng)OS底層的線程上,把調(diào)度委托給了操作系統(tǒng)。而操作系統(tǒng)比如Linux,它是多任務(wù)操作系統(tǒng),充分利用CPU的高性能,將CPU的時(shí)間分片,讓單個(gè)CPU實(shí)現(xiàn)"同時(shí)執(zhí)行"多任務(wù)的效果。

Linux的任務(wù)調(diào)度又采用搶占式輪轉(zhuǎn)調(diào)度,我們不考慮特權(quán)進(jìn)程的話,OS會(huì)選擇在CPU上占用的時(shí)間最少進(jìn)程,優(yōu)先在cpu上分配資源,其對(duì)應(yīng)的線程去執(zhí)行任務(wù),盡可能地維護(hù)任務(wù)調(diào)度公平。Running和?Ready?狀態(tài)的線程在CPU中切換狀態(tài)非常短暫。大概只有 0.01 秒這一量級(jí),區(qū)分開(kāi)來(lái)意義不大,java將這2個(gè)狀態(tài)統(tǒng)一用RUNNABLE來(lái)表示

thread.start()源碼解析

我們接下來(lái)看看為什么說(shuō)執(zhí)行thread.start()后,線程的才"真正的創(chuàng)建"

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()方法的源碼中,會(huì)去調(diào)用start0()方法,而start0()是private native void start0();JVM調(diào)用Native方法的話,會(huì)進(jìn)入到不受JVM控制的世界里

在Thread類實(shí)例化的同時(shí),會(huì)首先調(diào)用registerNatives方法,注冊(cè)本地Native方法,動(dòng)態(tài)綁定JVM方法

private static native void registerNatives();

? ? static {

? ? ? ? registerNatives();

? ? }

在Thread類中通過(guò)registerNatives將指定的本地方法綁定到指定函數(shù),比如start0本地方法綁定到JVM_StartThread函數(shù):

...

static JNINativeMethod methods[] = {

? ? {"start0",? ? ? ? ? "()V",? ? ? ? (void *)&JVM_StartThread},

? ? {"stop0",? ? ? ? ? ? "(" OBJ ")V", (void *)&JVM_StopThread},

? ? {"isAlive",? ? ? ? ? "()Z",? ? ? ? (void *)&JVM_IsThreadAlive},

? ? ...

JVM_StartThread?是JVM層函數(shù),拋去各種情況的處理,主要是通過(guò)?new JavaThread(&thread_entry, sz)來(lái)創(chuàng)建JVM線程對(duì)象

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))? JVMWrapper("JVM_StartThread");? JavaThread *native_thread = NULL;//表示是否有異常,當(dāng)拋出異常時(shí)需要獲取Heap_lock。boolthrow_illegal_thread_state=false;// 在發(fā)布jvmti事件之前,必須釋放Threads_lock// in Thread::start.{// 獲取 Threads_lock鎖MutexLockermu(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 runningjlongsize=java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));// 創(chuàng)建JVM線程(用JavaThread對(duì)象表示)size_tsz=size >0? (size_t) size :0;? ? ? native_thread =newJavaThread(&thread_entry, sz);? ? ? ...? ? }? }? ...? Thread::start(native_thread);//啟動(dòng)內(nèi)核線程JVM_END

源碼見(jiàn):https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/prims/jvm.cpp

我們?cè)賮?lái)看看JavaThread的實(shí)現(xiàn),發(fā)現(xiàn)內(nèi)部通過(guò)?os::create_thread(this, thr_type, stack_sz);來(lái)調(diào)用不同操作系統(tǒng)的創(chuàng)建線程方法創(chuàng)建線程。

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :? Thread()#ifINCLUDE_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_23os::ThreadTypethr_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);//調(diào)用不同操作系統(tǒng)的創(chuàng)建線程方法創(chuàng)建線程}

源碼見(jiàn):https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/runtime/thread.cpp

我們都知道Java是跨平臺(tái)的,但是native各種方法底層c/c++代碼對(duì)各平臺(tái)都需要有對(duì)應(yīng)的兼容,我們這邊以linux為例,其他平臺(tái)就大家自行去查閱了

bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {assert(thread->osthread() == NULL,"caller responsible");// Allocate the OSThread objectOSThread* osthread =newOSThread(NULL, NULL);if(osthread == NULL) {returnfalse;? }// set the correct thread stateosthread->set_thread_type(thr_type);// Initial state is ALLOCATED but not INITIALIZEDosthread->set_state(ALLOCATED);? thread->set_osthread(osthread);// init thread attributespthread_attr_t attr;? pthread_attr_init(&attr);? pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);// stack sizeif(os::Linux::supports_variable_stack_size()) {// calculate stack size if it's not specified by callerif(stack_size ==0) {? ? ? stack_size = os::Linux::default_stack_size(thr_type);switch(thr_type) {caseos::java_thread:// Java threads use ThreadStackSize which default value can be// changed with the flag -Xssassert(JavaThread::stack_size_at_create() >0,"this should be set");? ? ? ? stack_size = JavaThread::stack_size_at_create();break;caseos::compiler_thread:if(CompilerThreadStackSize >0) {? ? ? ? ? stack_size = (size_t)(CompilerThreadStackSize * K);break;? ? ? ? }// else fall through:// use VMThreadStackSize if CompilerThreadStackSize is not definedcaseos::vm_thread:caseos::pgc_thread:caseos::cgc_thread:caseos::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 pagepthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));? ThreadState state;? {// Serialize thread creation if we are running with fixed stack LinuxThreadsboollock=os::Linux::is_LinuxThreads() && !os::Linux::is_floating_stack();if(lock) {? ? ? os::Linux::createThread_lock()->lock_without_safepoint_check();? ? }? ? pthread_t tid;//通過(guò)pthread_create方法創(chuàng)建內(nèi)核級(jí)線程 !intret=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 farthread->set_osthread(NULL);? ? ? delete osthread;if(lock) os::Linux::createThread_lock()->unlock();returnfalse;? ? }// Store pthread info into the OSThreadosthread->set_pthread_id(tid);// Wait until child thread is either initialized or aborted{? ? ? Monitor* sync_with_child = osthread->startThread_lock();? ? ? MutexLockerExml(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 reachedif(state == ZOMBIE) {? ? ? thread->set_osthread(NULL);? ? ? delete osthread;returnfalse;? }// The thread is returned suspended (in state INITIALIZED),// and is started higher up in the call chainassert(state == INITIALIZED,"race condition");returntrue;}

源碼見(jiàn):https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/os/linux/vm/os_linux.cpp

主要通過(guò)pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread),它是unix 創(chuàng)建線程的方法,linux也繼承了。調(diào)用后在linux系統(tǒng)中會(huì)創(chuàng)建一個(gè)內(nèi)核級(jí)的線程。也就是說(shuō)這個(gè)時(shí)候操作系統(tǒng)中線程才真正地誕生

更多精彩文章在公眾號(hào)「小牛呼嚕嚕

但此時(shí)線程才誕生,那是怎么啟動(dòng)的?我們回到JVM_StartThread源碼中,Thread::start(native_thread)很明顯這行代碼就表示啟動(dòng)native_thread = new JavaThread(&thread_entry, sz)創(chuàng)建的線程,我們來(lái)繼續(xù)看看其源碼

voidThread::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()) {// 設(shè)置線程狀態(tài)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),執(zhí)行該方法,操作系統(tǒng)會(huì)去啟動(dòng)指定的線程

voidos::start_thread(Thread* thread) {// guard suspend/resumeMutexLockerExml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);? OSThread* osthread = thread->osthread();? osthread->set_state(RUNNABLE);? pd_start_thread(thread);}

當(dāng)操作系統(tǒng)的線程啟動(dòng)完之后,我們?cè)倩氐絧thread_create(&tid, &attr, (void* (*)(void*)) java_start, thread),會(huì)去java_start這個(gè)線程入口函數(shù)進(jìn)行OS內(nèi)核級(jí)線程的初始化,并開(kāi)始啟動(dòng)JavaThread

// Thread start routine for all newly created threadsstaticvoid*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.staticintcounter=0;intpid=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 aboveif(!_thread_safety_check(thread)) {// notify parent threadMutexLockerExml(sync, Mutex::_no_safepoint_check_flag);? ? osthread->set_state(ZOMBIE);? ? sync->notify_all();returnNULL;? }// thread_id is kernel thread id (similar to Solaris LWP id)osthread->set_thread_id(os::Linux::gettid());if(UseNUMA) {intlgrp_id=os::numa_get_group_id();if(lgrp_id != -1) {? ? ? thread->set_lgrp_id(lgrp_id);? ? }? }// initialize signal mask for this threados::Linux::hotspot_sigmask(thread);// initialize floating point control registeros::Linux::init_thread_fpu_state();// handshaking with parent thread{? ? MutexLockerExml(sync, Mutex::_no_safepoint_check_flag);// notify parent threadosthread->set_state(INITIALIZED);? ? sync->notify_all();// 等待,直到操作系統(tǒng)級(jí)線程全部啟動(dòng)while(osthread->get_state() == INITIALIZED) {? ? ? sync->wait(Mutex::_no_safepoint_check_flag);? ? }? }// 開(kāi)始運(yùn)行JavaThread::runthread->run();return0;}

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

thread->run()其實(shí)就是JavaThread::run()也表明方法開(kāi)始回調(diào),從OS層方法回到JVM層方法

,我們?cè)賮?lái)看下其實(shí)現(xiàn):

// 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對(duì)象中傳入的entry_point為Thread對(duì)象的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)中參數(shù)entry_point是外部傳入,那我們想想JavaThread是什么時(shí)候?qū)嵗模?/p>

沒(méi)錯(cuò),就是我們一開(kāi)始的JVM_StartThread中native_thread = new JavaThread(&thread_entry, sz);

也就是說(shuō)this->entry_point()(this, this)實(shí)際上是回調(diào)的thread_entry方法

thread_entry源碼:

staticvoidthread_entry(JavaThread* thread, TRAPS){? HandleMarkhm(THREAD);? Handleobj(THREAD, thread->threadObj());? JavaValueresult(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

通過(guò)JavaCalls::call_virtual方法,又從JVM層 回到了Java語(yǔ)言層 ,即MyThread thread = new MyThread(); thread.start();

一切又回到了起點(diǎn),這就是Javathread.start()內(nèi)部完整的一個(gè)流程,HotSpot虛擬機(jī)實(shí)現(xiàn)的Java線程其實(shí)是對(duì)Linux內(nèi)核級(jí)線程的直接映射,將Java涉及到的所有線程調(diào)度、內(nèi)存分配都交由操作系統(tǒng)進(jìn)行管理。

線程終止?fàn)顟B(tài)

線程終止?fàn)顟B(tài)(TERMINATED),表示該線程已經(jīng)運(yùn)行完畢。

當(dāng)一個(gè)線程執(zhí)行完畢,或者主線程的main()方法完成時(shí),我們就認(rèn)為它終止了。終止的線程無(wú)法在被使用,如果調(diào)用start()方法,會(huì)拋出java.lang.IllegalThreadStateException異常,這一點(diǎn)我們可以從start源碼中很容易地得到

publicsynchronizedvoidstart(){if(threadStatus !=0)thrownewIllegalThreadStateException();? ? ...}

線程阻塞狀態(tài)

線程阻塞狀態(tài)(BLOCKED),需要等待鎖釋放或者說(shuō)獲取鎖失敗時(shí),線程阻塞

publicclassBlockedThreadimplementsRunnable{@Overridepublicvoidrun(){synchronized(BlockedThread.class){while(true){? ? ? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? }? ? }}

從Thread源碼的注釋中,我們可以知道等待鎖釋放或者說(shuō)獲取鎖失敗,主要有下面3中情況:

進(jìn)入 synchronized 方法時(shí)

進(jìn)入 synchronized 塊時(shí)

調(diào)用 wait 后, 重新進(jìn)入 synchronized 方法/塊時(shí)

其中第三種情況,大家可以先思考一下,我們留在下文線程等待狀態(tài)再詳細(xì)展開(kāi)

線程等待狀態(tài)

線程等待狀態(tài)(WAITING),表示該線程需要等待其他線程做出一些特定動(dòng)作(通知或中斷)。

wait/notify/notifyAll

我們緊接著上一小節(jié),調(diào)用?wait 后, 重新進(jìn)入synchronized 方法/塊時(shí),我們來(lái)看看期間發(fā)生了什么?

當(dāng)線程1調(diào)用對(duì)象A的wait方法后,會(huì)釋放當(dāng)前的鎖,然后讓出CPU時(shí)間片,線程會(huì)進(jìn)入該對(duì)象的等待隊(duì)列中,線程狀態(tài)變?yōu)?等待狀態(tài)WAITING。

當(dāng)另一個(gè)線程2調(diào)用了對(duì)象A的notify()/notifyAll()方法

notify()方法只會(huì)喚醒沉睡的線程,不會(huì)立即釋放之前占有的對(duì)象A的鎖,必須執(zhí)行完notify()方法所在的synchronized代碼塊后才釋放。所以在編程中,盡量在使用了notify/notifyAll()后立即退出臨界區(qū)

線程1收到通知后退出等待隊(duì)列,并進(jìn)入線程運(yùn)行狀態(tài)RUNNABLE,等待 CPU 時(shí)間片分配, 進(jìn)而執(zhí)行后續(xù)操作,接著線程1重新進(jìn)入 synchronized 方法/塊時(shí),競(jìng)爭(zhēng)不到鎖,線程狀態(tài)變?yōu)榫€程阻塞狀態(tài)BLOCKED。如果競(jìng)爭(zhēng)到鎖,就直接接著運(yùn)行。線程等待狀態(tài) 切換到線程阻塞狀態(tài),無(wú)法直接切換,需要經(jīng)過(guò)線程運(yùn)行狀態(tài)。

我們?cè)賮?lái)看一個(gè)例子,鞏固鞏固:

publicclassWaitNotifyTest{publicstaticvoidmain(String[] args){ObjectA=newObject();newThread(newRunnable() {@Overridepublicvoidrun(){? ? ? ? ? ? ? ? System.out.println("線程1等待獲取 對(duì)象A的鎖...");synchronized(A) {try{? ? ? ? ? ? ? ? ? ? ? ? System.out.println("線程1獲取了 對(duì)象A的鎖");? ? ? ? ? ? ? ? ? ? ? ? Thread.sleep(3000);? ? ? ? ? ? ? ? ? ? ? ? System.out.println("線程1開(kāi)始運(yùn)行wait()方法進(jìn)行等待,進(jìn)入到等待隊(duì)列......");? ? ? ? ? ? ? ? ? ? ? ? A.wait();? ? ? ? ? ? ? ? ? ? ? ? System.out.println("線程1等待結(jié)束");? ? ? ? ? ? ? ? ? ? }catch(InterruptedException e) {? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }).start();newThread(newRunnable() {@Overridepublicvoidrun(){? ? ? ? ? ? ? ? System.out.println("線程2等待獲取 對(duì)象A的鎖...");synchronized(A) {? ? ? ? ? ? ? ? ? ? System.out.println("線程2獲取了 對(duì)象A的鎖");try{? ? ? ? ? ? ? ? ? ? ? ? Thread.sleep(3000);? ? ? ? ? ? ? ? ? ? }catch(InterruptedException e) {? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? System.out.println("線程2將要運(yùn)行notify()方法進(jìn)行喚醒線程1");? ? ? ? ? ? ? ? ? ? A.notify();? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }).start();? ? }}

結(jié)果:

線程1等待獲取 對(duì)象A的鎖...線程1獲取了 對(duì)象A的鎖線程2等待獲取 對(duì)象A的鎖...線程1開(kāi)始運(yùn)行wait()方法進(jìn)行等待,進(jìn)入到等待隊(duì)列......線程2獲取了 對(duì)象A的鎖線程2將要運(yùn)行notify()方法進(jìn)行喚醒線程1線程1等待結(jié)束

需要注意的是,wait/notify/notifyAll 只能在synchronized修飾的方法、塊中使用,?notify 是只隨機(jī)喚醒一個(gè)線程,而 notifyAll 是喚醒所有等待隊(duì)列中的線程

join

Thread類中的join方法的主要作用能讓線程之間的并行執(zhí)行變?yōu)榇袌?zhí)行,當(dāng)前線程等該加入該線程后面,等待該線程終止

publicstaticvoidmain(String[] args){Threadthread=newThread();? thread.start();? thread.join();? ...}

上面一個(gè)例子表示,程序在main主線程中調(diào)用thread線程的join方法,意味著main線程放棄CPU時(shí)間片(主線程會(huì)變成 WAITING 狀態(tài)),并返回thread線程,繼續(xù)執(zhí)行直到線程thread執(zhí)行完畢,換句話說(shuō)在主線程執(zhí)行過(guò)程中,插入thread線程,還得等thread線程執(zhí)行完后,才輪到主線程繼續(xù)執(zhí)行

如果查看JDKthread.join()底層實(shí)現(xiàn),會(huì)發(fā)現(xiàn)其實(shí)內(nèi)部封裝了wait(),notifyAll()

park/unpark

LockSupport.park() 掛起當(dāng)前線程;LockSupport.unpark(暫停線程對(duì)象) 恢復(fù)某個(gè)線程

packagecom.zj.ideaprojects.demo.test3;importjava.util.concurrent.Executors;importjava.util.concurrent.locks.LockSupport;publicclassThreadLockSupportTest{publicstaticvoidmain(String[] args)throwsInterruptedException {Threadthread=newThread(() -> {? ? ? ? ? ? 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);? ? }}

結(jié)果:

start.....

park....

unpark....

resume.....

當(dāng)程序調(diào)用LockSupport.park(),會(huì)讓當(dāng)前線程A的線程狀態(tài)會(huì)從 RUNNABLE 變成 WAITING,然后main主線程調(diào)用LockSupport.unpark(thread),讓指定的線程即線程A,從 WAITING 回到 RUNNABLE 。我們可以發(fā)現(xiàn)

park/unpark和wait/notify/notifyAll很像,但是他們有以下的區(qū)別:

wait,notify 和 notifyAll 必須事先獲取對(duì)象鎖,而 unpark 不必

park、unpark 可以先 unpark ,而 wait、notify 不能先 notify,必須先wait

unpark 可以精準(zhǔn)喚醒某一個(gè)確定的線程。而 notify 只能隨機(jī)喚醒一個(gè)等待線程,notifyAll 是喚醒所以等待線程,就不那么精確

超時(shí)等待狀態(tài)

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

這部分比較簡(jiǎn)單,它和線程等待狀態(tài)(WAITING)狀態(tài) 非常相似,區(qū)別就是方法的參數(shù)舒服傳入限制時(shí)間,在?Timed Waiting狀態(tài)時(shí)會(huì)等待超時(shí),之后由系統(tǒng)喚醒,或者也可以提前被通知喚醒如?notify

相關(guān)方法主要有:

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

需要注意的是Thread.sleep(long),當(dāng)線程執(zhí)行sleep方法時(shí),不會(huì)釋放當(dāng)前的鎖(如果當(dāng)前線程進(jìn)入了同步鎖),也不會(huì)讓出CPU。sleep(long)可以用指定時(shí)間使它自動(dòng)喚醒過(guò)來(lái),如果時(shí)間不到只能調(diào)用interrupt方法強(qiáng)行打斷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容