當(dāng)我們想要線程按照一定的執(zhí)行順序執(zhí)行峡钓,通常最簡單直接的方式就是使用到j(luò)oin方法。
join屬于對象方法若河,通過線程實(shí)例可以調(diào)用能岩。他的作用是阻塞等待線程執(zhí)行結(jié)束,再執(zhí)行后續(xù)的代碼萧福。接下來展開說明join的實(shí)現(xiàn)原理拉鹃。
線程的waiting狀態(tài)
JAVA中能時(shí)線程進(jìn)入waiting的方法有obj.wait(),thread.join(),LockSupport.park()。waiting狀態(tài)的線程需要被喚醒轉(zhuǎn)化為runnable狀態(tài),等待CPU調(diào)度膏燕。而喚醒線程的方法通過執(zhí)行notify/notifyAll方法钥屈,JVM調(diào)用操作系統(tǒng)命令實(shí)現(xiàn)。
我們來看下join方法
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
// 參數(shù)為0 調(diào)用
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
// 實(shí)際上調(diào)用的是Object的wait()方法
public final native void wait(long timeout) throws InterruptedException;
可以知道坝辫,join方法是通過不斷的循環(huán)調(diào)用wait()方法是實(shí)現(xiàn)等待篷就,那么什么時(shí)候會(huì)喚醒線程繼續(xù)執(zhí)行?join的作用是等待線程執(zhí)行完成阀溶,那么可以猜想腻脏,再線程結(jié)束執(zhí)行時(shí)是否會(huì)喚醒線程?
我們看下Thread的start方法,實(shí)際上調(diào)用的是native方法start0()套耕,這時(shí)需要看下JDK源碼下Thread.c找到start0方法定義
{"start0", "()V", (void *)&JVM_StartThread},
再查看hotspot源碼栏笆,跟蹤到j(luò)vm.cpp
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.
{
// Ensure that the C++ Thread and OSThread structures aren't freed before
// we operate.
MutexLocker mu(Threads_lock);
// Since JDK 5 the java.lang.Thread threadStatus is used to prevent
// re-starting an already started thread, so we should usually find
// that the JavaThread is null. However for a JNI attached thread
// there is a small window between the Thread object being created
// (with its JavaThread set) and the update to its threadStatus, so we
// have to check for this
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));
// Allocate the C++ Thread structure and create the native thread. The
// stack size retrieved from java is signed, but the constructor takes
// size_t (an unsigned type), so avoid passing negative values which would
// result in really large stacks.
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");
}
// 跟蹤這個(gè)方法 hotspot->thread.cpp
Thread::start(native_thread);
JVM_END
thread.cpp:
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()) {
// Initialize the thread state to RUNNABLE before starting this thread.
// Can not set it after the thread started because we do not know the
// exact thread state at that time. It could be in MONITOR_WAIT or
// in SLEEPING or some other state.
java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
java_lang_Thread::RUNNABLE);
}
// 跟蹤hotspot os.cpp
os::start_thread(thread);
}
}
os.cpp:
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);
// 啟動(dòng)的方法
pd_start_thread(thread);
}
執(zhí)行啟動(dòng)線程命令,則我們可以猜想下虐呻,再線程結(jié)束的時(shí)候是否會(huì)喚醒線程。跟蹤到線程結(jié)束的地方(由于hotspot源碼調(diào)用關(guān)系較多,省略跟蹤過程)更振。最終調(diào)用的方法是hotspot\src\os\windows\vm\os_windows.cpp#os::pd_start_thread
,linux對應(yīng)os_linux.cpp
// windows
void os::pd_start_thread(Thread* thread) {
// 調(diào)用操作系統(tǒng)回復(fù)線程命令喚醒
DWORD ret = ResumeThread(thread->osthread()->thread_handle());
// Returns previous suspend state:
// 0: Thread was not suspended
// 1: Thread is running now
// >1: Thread is still suspended.
assert(ret != SYS_THREAD_ERROR, "StartThread failed"); // should propagate back
}
//linux
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);
// notify 調(diào)用操作系統(tǒng)命令喚醒線程
sync_with_child->notify();
}
可知join命令實(shí)質(zhì)上是通過,wait/notify操作實(shí)現(xiàn)的線程等待饭尝。