Thread.join
加了join,表示join的线程的修改对于join之外的代码是可见的。
代码示例:
public class JoinDemo {
private static int i = 1000;
public static void main(String[] args) {
new Thread(()->{
i = 3000;
}).start();
System.out.println("i="+i);
}
}
我们在main线程中定义一个i
,初始值为1000。
在main方法中创建一个线程,将i值设置为3000,启动线程。
然后main方法打印 i
的值。
请问,i
值为多少?
我们执行后的输出结果为:
i=1000
这是由于main线程先于thread线程,所以输出的i
值为1000。
如果我们希望 Thread线程中的执行结果对main线程可见,怎么办?
使用 thread.join()
,如下所示:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
i = 3000;
});
thread.start();
thread.join();
System.out.println("i="+i);
}
执行结果:
i=3000
所以说,如果我们希望结果是可见的话,可以通过join来做。
那么join的实现原理是什么?请看下图:
main线程中,创建了一个线程t1;
t1.start() 启动线程;
t1线程修改了i
的值为3000;而且让修改可见。说明t1线程阻塞的main线程,使它无法打印。
t1线程继续执行直到终止。
t1终止后,去唤醒被它阻塞的线程。main线程继续执行,打印出 i=3000
。
有阻塞,就一定要唤醒,否则线程无法释放。我们是怎么触发唤醒的?
我们进入join的源码:
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");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
join是个synchronized的方法,里面有个 wait(0)
方法来阻塞。
那它是如何唤醒的呢?
我们去看hotspot的源码,其中的thread.cpp:
// For any new cleanup additions, please check to see if they need to be applied to
// cleanup_failed_attach_current_thread as well.
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
assert(this == JavaThread::current(), "thread consistency check");
HandleMark hm(this);
Handle uncaught_exception(this, this->pending_exception());
this->clear_pending_exception();
Handle threadObj(this, this->threadObj());
assert(threadObj.not_null(), "Java thread object should be created");
if (get_thread_profiler() != NULL) {
get_thread_profiler()->disengage();
ResourceMark rm;
get_thread_profiler()->print(get_thread_name());
}
// FIXIT: This code should be moved into else part, when reliable 1.2/1.3 check is in place
{
EXCEPTION_MARK;
CLEAR_PENDING_EXCEPTION;
}
// FIXIT: The is_null check is only so it works better on JDK1.2 VM's. This
// has to be fixed by a runtime query method
if (!destroy_vm || JDK_Version::is_jdk12x_version()) {
// JSR-166: change call from from ThreadGroup.uncaughtException to
// java.lang.Thread.dispatchUncaughtException
if (uncaught_exception.not_null()) {
Handle group(this, java_lang_Thread::threadGroup(threadObj()));
{
EXCEPTION_MARK;
// Check if the method Thread.dispatchUncaughtException() exists. If so
// call it. Otherwise we have an older library without the JSR-166 changes,
// so call ThreadGroup.uncaughtException()
KlassHandle recvrKlass(THREAD, threadObj->klass());
CallInfo callinfo;
KlassHandle thread_klass(THREAD, SystemDictionary::Thread_klass());
LinkResolver::resolve_virtual_call(callinfo, threadObj, recvrKlass, thread_klass,
vmSymbols::dispatchUncaughtException_name(),
vmSymbols::throwable_void_signature(),
KlassHandle(), false, false, THREAD);
CLEAR_PENDING_EXCEPTION;
methodHandle method = callinfo.selected_method();
if (method.not_null()) {
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,
threadObj, thread_klass,
vmSymbols::dispatchUncaughtException_name(),
vmSymbols::throwable_void_signature(),
uncaught_exception,
THREAD);
} else {
KlassHandle thread_group(THREAD, SystemDictionary::ThreadGroup_klass());
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,
group, thread_group,
vmSymbols::uncaughtException_name(),
vmSymbols::thread_throwable_void_signature(),
threadObj, // Arg 1
uncaught_exception, // Arg 2
THREAD);
}
if (HAS_PENDING_EXCEPTION) {
ResourceMark rm(this);
jio_fprintf(defaultStream::error_stream(),
"\nException: %s thrown from the UncaughtExceptionHandler"
" in thread \"%s\"\n",
pending_exception()->klass()->external_name(),
get_thread_name());
CLEAR_PENDING_EXCEPTION;
}
}
}
// Called before the java thread exit since we want to read info
// from java_lang_Thread object
EventThreadEnd event;
if (event.should_commit()) {
event.set_javalangthread(java_lang_Thread::thread_id(this->threadObj()));
event.commit();
}
// Call after last event on thread
EVENT_THREAD_EXIT(this);
// Call Thread.exit(). We try 3 times in case we got another Thread.stop during
// the execution of the method. If that is not enough, then we don't really care. Thread.stop
// is deprecated anyhow.
if (!is_Compiler_thread()) {
int count = 3;
while (java_lang_Thread::threadGroup(threadObj()) != NULL && (count-- > 0)) {
EXCEPTION_MARK;
JavaValue result(T_VOID);
KlassHandle thread_klass(THREAD, SystemDictionary::Thread_klass());
JavaCalls::call_virtual(&result,
threadObj, thread_klass,
vmSymbols::exit_method_name(),
vmSymbols::void_method_signature(),
THREAD);
CLEAR_PENDING_EXCEPTION;
}
}
// notify JVMTI
if (JvmtiExport::should_post_thread_life()) {
JvmtiExport::post_thread_end(this);
}
// We have notified the agents that we are exiting, before we go on,
// we must check for a pending external suspend request and honor it
// in order to not surprise the thread that made the suspend request.
while (true) {
{
MutexLockerEx ml(SR_lock(), Mutex::_no_safepoint_check_flag);
if (!is_external_suspend()) {
set_terminated(_thread_exiting);
ThreadService::current_thread_exiting(this);
break;
}
// Implied else:
// Things get a little tricky here. We have a pending external
// suspend request, but we are holding the SR_lock so we
// can't just self-suspend. So we temporarily drop the lock
// and then self-suspend.
}
ThreadBlockInVM tbivm(this);
java_suspend_self();
// We're done with this suspend request, but we have to loop around
// and check again. Eventually we will get SR_lock without a pending
// external suspend request and will be able to mark ourselves as
// exiting.
}
// no more external suspends are allowed at this point
} else {
// before_exit() has already posted JVMTI THREAD_END events
}
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
ensure_join(this);
assert(!this->has_pending_exception(), "ensure_join should have cleared");
// 6282335 JNI DetachCurrentThread spec states that all Java monitors
// held by this thread must be released. A detach operation must only
// get here if there are no Java frames on the stack. Therefore, any
// owned monitors at this point MUST be JNI-acquired monitors which are
// pre-inflated and in the monitor cache.
//
// ensure_join() ignores IllegalThreadStateExceptions, and so does this.
if (exit_type == jni_detach && JNIDetachReleasesMonitors) {
assert(!this->has_last_Java_frame(), "detaching with Java frames?");
ObjectSynchronizer::release_monitors_owned_by_thread(this);
assert(!this->has_pending_exception(), "release_monitors should have cleared");
}
// These things needs to be done while we are still a Java Thread. Make sure that thread
// is in a consistent state, in case GC happens
assert(_privileged_stack_top == NULL, "must be NULL when we get here");
if (active_handles() != NULL) {
JNIHandleBlock* block = active_handles();
set_active_handles(NULL);
JNIHandleBlock::release_block(block);
}
if (free_handle_block() != NULL) {
JNIHandleBlock* block = free_handle_block();
set_free_handle_block(NULL);
JNIHandleBlock::release_block(block);
}
// These have to be removed while this is still a valid thread.
remove_stack_guard_pages();
if (UseTLAB) {
tlab().make_parsable(true); // retire TLAB
}
if (JvmtiEnv::environments_might_exist()) {
JvmtiExport::cleanup_thread(this);
}
// We must flush any deferred card marks before removing a thread from
// the list of active threads.
Universe::heap()->flush_deferred_store_barrier(this);
assert(deferred_card_mark().is_empty(), "Should have been flushed");
#if INCLUDE_ALL_GCS
// We must flush the G1-related buffers before removing a thread
// from the list of active threads. We must do this after any deferred
// card marks have been flushed (above) so that any entries that are
// added to the thread's dirty card queue as a result are not lost.
if (UseG1GC) {
flush_barrier_queues();
}
#endif // INCLUDE_ALL_GCS
// Remove from list of active threads list, and notify VM thread if we are the last non-daemon thread
Threads::remove(this);
}
线程终止的时候,会调用JavaThread::exit 方法,这个退出方法中,有个清理的工作:
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
ensure_join(this);
唤醒当前线程对象上的阻塞线程,这是在调用exit之后被完成的。
我们来看 ensure_join 方法具体做了些什么?
static void ensure_join(JavaThread* thread) {
// We do not need to grap the Threads_lock, since we are operating on ourself.
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), "java thread object must exist");
ObjectLocker lock(threadObj, thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
// Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// Clear the native thread instance - this makes isAlive return false and allows the join()
// to complete once we've done the notify_all below
java_lang_Thread::set_thread(threadObj(), NULL);
lock.notify_all(thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}
其中关键的是 lock.notify_all(thread);
wait方法锁的是当前t1实例,t1退出的时候,拿到t1实例,然后拿到t1的锁,然后notify_all。
以上就是我们对thread.join()的全部解读。