大家好,我是呼嚕嚕,最近一直在梳理JAVA并發(fā),但內(nèi)容雜且偏晦澀,今天我們一起來聊聊Java 線程的狀態(tài)及轉(zhuǎn)換 先來夯實一下基礎(chǔ),萬丈高樓平地起,路還是得慢慢走。
Java線程的生命周期
我們先來看下Java線程的生命周期圖:
上圖也是本文的大綱,我們下面依次聊聊java各個線程狀態(tài)及其他們的轉(zhuǎn)換。
線程初始狀態(tài)
線程初始狀態(tài)(NEW): 當前線程處于線程被創(chuàng)建出來但沒有被調(diào)用start()
在Java線程的時間中,關(guān)于線程的一切的起點是從Thread 類的對象的創(chuàng)建開始,一般實現(xiàn)Runnable接口 或者 繼承Thread類的類,實例化一個對象出來,線程就進入了初始狀態(tài)
Thread thread = new Thread()
由于線程在我們操作系統(tǒng)中也是非常寶貴的資源,在實際開發(fā)中,我們常常用線程池來重復利用現(xiàn)有的線程來執(zhí)行任務,避免多次創(chuàng)建和銷毀線程,從而降低創(chuàng)建和銷毀線程過程中的代價。Java 給我們提供了 Executor 接口來使用線程池,查看其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源碼中都是通過threadStatus的值來表示的
/* Java thread status for tools,
* initialized to indicate thread 'not yet started'
*/
private volatile int threadStatus = 0;
State 和 threadStatus 通過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中只是設置了線程的狀態(tài),操作系統(tǒng)中并沒有的實際線程的創(chuàng)建
線程運行狀態(tài)
線程運行狀態(tài)(RUNNABLE),線程被調(diào)用了start()等待運行的狀態(tài)
在linux操作系統(tǒng)層面,包含Running和 Ready狀態(tài)。其中Ready狀態(tài)是等待 CPU 時間片。現(xiàn)今主流的JVM,比如hotspot虛擬機都是把Java 線程,映射到操作系統(tǒng)OS底層的線程上,把調(diào)度委托給了操作系統(tǒng)。而操作系統(tǒng)比如Linux,它是多任務操作系統(tǒng),充分利用CPU的高性能,將CPU的時間分片,讓單個CPU實現(xiàn)"同時執(zhí)行"多任務的效果。
Linux的任務調(diào)度又采用搶占式輪轉(zhuǎn)調(diào)度,我們不考慮特權(quán)進程的話,OS會選擇在CPU上占用的時間最少進程,優(yōu)先在cpu上分配資源,其對應的線程去執(zhí)行任務,盡可能地維護任務調(diào)度公平。Running和 Ready狀態(tài)的線程在CPU中切換狀態(tài)非常短暫。大概只有 0.01 秒這一量級,區(qū)分開來意義不大,java將這2個狀態(tài)統(tǒng)一用RUNNABLE來表示
thread.start()源碼解析
我們接下來看看為什么說執(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()方法的源碼中,會去調(diào)用start0()方法,而start0()是private native void start0();JVM調(diào)用Native方法的話,會進入到不受JVM控制的世界里
在Thread類實例化的同時,會首先調(diào)用registerNatives方法,注冊本地Native方法,動態(tài)綁定JVM方法
private static native void registerNatives();
static {
registerNatives();
}
在Thread類中通過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},
...
源碼見:
http://hg.openjdk.java.NET/jdk8u/jdk8u60/jdk/file/935758609767/src/share/native/java/lang/Thread.c
JVM_StartThread是JVM層函數(shù),拋去各種情況的處理,主要是通過new JavaThread(&thread_entry, sz)來創(chuàng)建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;
// 在發(fā)布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));
// 創(chuàng)建JVM線程(用JavaThread對象表示)
size_t sz = size > 0 ? (size_t) size : 0;
native_thread = new JavaThread(&thread_entry, sz);
...
}
}
...
Thread::start(native_thread);//啟動內(nèi)核線程
JVM_END
源碼見:
https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/prims/jvm.cpp
我們再來看看JavaThread的實現(xiàn),發(fā)現(xiàn)內(nèi)部通過 os::create_thread(this, thr_type, stack_sz);來調(diào)用不同操作系統(tǒng)的創(chuàng)建線程方法創(chuàng)建線程。
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);//調(diào)用不同操作系統(tǒng)的創(chuàng)建線程方法創(chuàng)建線程
}
源碼見:
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方法創(chuàng)建內(nèi)核級線程 !
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 創(chuàng)建線程的方法,linux也繼承了。調(diào)用后在linux系統(tǒng)中會創(chuàng)建一個內(nèi)核級的線程。也就是說這個時候操作系統(tǒng)中線程才真正地誕生
但此時線程才誕生,那是怎么啟動的?我們回到JVM_StartThread源碼中,Thread::start(native_thread)很明顯這行代碼就表示啟動native_thread = new JavaThread(&thread_entry, sz)創(chuàng)建的線程,我們來繼續(xù)看看其源碼
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()) {
// 設置線程狀態(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)會去啟動指定的線程
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);
}
當操作系統(tǒng)的線程啟動完之后,我們再回到pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread),會去java_start這個線程入口函數(shù)進行OS內(nèi)核級線程的初始化,并開始啟動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();
// 等待,直到操作系統(tǒng)級線程全部啟動
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()也表明方法開始回調(diào),從OS層方法回到JVM層方法 ,我們再來看下其實現(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對象中傳入的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)中參數(shù)entry_point是外部傳入,那我們想想JavaThread是什么時候?qū)嵗模?沒錯,就是我們一開始的JVM_StartThread中native_thread = new JavaThread(&thread_entry, sz); 也就是說this->entry_point()(this, this)實際上是回調(diào)的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()內(nèi)部完整的一個流程,HotSpot虛擬機實現(xiàn)的Java線程其實是對Linux內(nèi)核級線程的直接映射,將Java涉及到的所有線程調(diào)度、內(nèi)存分配都交由操作系統(tǒng)進行管理。
線程終止狀態(tài)
線程終止狀態(tài)(TERMINATED),表示該線程已經(jīng)運行完畢。
當一個線程執(zhí)行完畢,或者主線程的main()方法完成時,我們就認為它終止了。終止的線程無法在被使用,如果調(diào)用start()方法,會拋出
java.lang.IllegalThreadStateException異常,這一點我們可以從start源碼中很容易地得到
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
...
}
線程阻塞狀態(tài)
線程阻塞狀態(tài)(BLOCKED),需要等待鎖釋放或者說獲取鎖失敗時,線程阻塞
public class BlockedThread implements Runnable {
@Override
public void run() {
synchronized (BlockedThread.class){
while (true){
}
}
}
}
從Thread源碼的注釋中,我們可以知道等待鎖釋放或者說獲取鎖失敗,主要有下面3中情況:
- 進入 synchronized 方法時
- 進入 synchronized 塊時
- 調(diào)用 wait 后, 重新進入 synchronized 方法/塊時
其中第三種情況,大家可以先思考一下,我們留在下文線程等待狀態(tài)再詳細展開
線程等待狀態(tài)
線程等待狀態(tài)(WAITING),表示該線程需要等待其他線程做出一些特定動作(通知或中斷)。
wait/notify/notifyAll
我們緊接著上一小節(jié),調(diào)用wait 后, 重新進入synchronized 方法/塊時,我們來看看期間發(fā)生了什么?
當線程1調(diào)用對象A的wait方法后,會釋放當前的鎖,然后讓出CPU時間片,線程會進入該對象的等待隊列中,線程狀態(tài)變?yōu)?等待狀態(tài)WAITING。 當另一個線程2調(diào)用了對象A的notify()/notifyAll()方法
notify()方法只會喚醒沉睡的線程,不會立即釋放之前占有的對象A的鎖,必須執(zhí)行完notify()方法所在的synchronized代碼塊后才釋放。所以在編程中,盡量在使用了notify/notifyAll()后立即退出臨界區(qū)
線程1收到通知后退出等待隊列,并進入線程運行狀態(tài)RUNNABLE,等待 CPU 時間片分配, 進而執(zhí)行后續(xù)操作,接著線程1重新進入 synchronized 方法/塊時,競爭不到鎖,線程狀態(tài)變?yōu)榫€程阻塞狀態(tài)BLOCKED。如果競爭到鎖,就直接接著運行。線程等待狀態(tài) 切換到線程阻塞狀態(tài),無法直接切換,需要經(jīng)過線程運行狀態(tài)。
我們再來看一個例子,鞏固鞏固:
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等待結(jié)束");
} 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();
}
}
結(jié)果:
線程1等待獲取 對象A的鎖...
線程1獲取了 對象A的鎖
線程2等待獲取 對象A的鎖...
線程1開始運行wait()方法進行等待,進入到等待隊列......
線程2獲取了 對象A的鎖
線程2將要運行notify()方法進行喚醒線程1
線程1等待結(jié)束
需要注意的是,wait/notify/notifyAll 只能在synchronized修飾的方法、塊中使用, notify 是只隨機喚醒一個線程,而 notifyAll 是喚醒所有等待隊列中的線程
join
Thread類中的join方法的主要作用能讓線程之間的并行執(zhí)行變?yōu)榇袌?zhí)行,當前線程等該加入該線程后面,等待該線程終止
public static void main(String[] args) {
Thread thread = new Thread();
thread.start();
thread.join();
...
}
上面一個例子表示,程序在main主線程中調(diào)用thread線程的join方法,意味著main線程放棄CPU時間片(主線程會變成 WAITING 狀態(tài)),并返回thread線程,繼續(xù)執(zhí)行直到線程thread執(zhí)行完畢,換句話說在主線程執(zhí)行過程中,插入thread線程,還得等thread線程執(zhí)行完后,才輪到主線程繼續(xù)執(zhí)行
如果查看JDKthread.join()底層實現(xiàn),會發(fā)現(xiàn)其實內(nèi)部封裝了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);
}
}
結(jié)果:
start.....
park....
unpark....
resume.....
當程序調(diào)用LockSupport.park(),會讓當前線程A的線程狀態(tài)會從 RUNNABLE 變成 WAITING,然后main主線程調(diào)用LockSupport.unpark(thread),讓指定的線程即線程A,從 WAITING 回到 RUNNABLE 。我們可以發(fā)現(xiàn) park/unpark和wait/notify/notifyAll很像,但是他們有以下的區(qū)別:
- wait,notify 和 notifyAll 必須事先獲取對象鎖,而 unpark 不必
- park、unpark 可以先 unpark ,而 wait、notify 不能先 notify,必須先wait
- unpark 可以精準喚醒某一個確定的線程。而 notify 只能隨機喚醒一個等待線程,notifyAll 是喚醒所以等待線程,就不那么精確
超時等待狀態(tài)
超時等待狀態(tài)(TIMED_WAITING),也叫限期等待,可以在指定的時間后自行返回而不是像 WAITING 那樣一直等待。
這部分比較簡單,它和線程等待狀態(tài)(WAITING)狀態(tài) 非常相似,區(qū)別就是方法的參數(shù)舒服傳入限制時間,在 Timed Waiting狀態(tài)時會等待超時,之后由系統(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),當線程執(zhí)行sleep方法時,不會釋放當前的鎖(如果當前線程進入了同步鎖),也不會讓出CPU。sleep(long)可以用指定時間使它自動喚醒過來,如果時間不到只能調(diào)用interrupt方法強行打斷。
參考資料:
https://hg.openjdk.java.net/jdk8u 《并發(fā)編程的藝術(shù)》 https://www.jianshu.com/p/216a41352fd8
全文完,感謝您的閱讀,如果我的文章對你有所幫助的話,還請點個免費的贊,你的支持會激勵我輸出更高質(zhì)量的文章,感謝!
原文鏡像:
https://mp.weixin.qq.com/s/YcRaiLAAUDom2Kaq3Yw1Cw
計算機內(nèi)功、源碼解析、科技故事、項目實戰(zhàn)、面試八股等更多硬核文章,首發(fā)于公眾號「小牛呼嚕嚕」,我們下期再見!