03.关于线程你必须知道的8个问题(中)
作者:互联网
我们一起学习了如何创建线程,以及Java中线程状态,那么今天就来学习Thread类的核心方法。
Tips:
- Java及JVM源码基于Java 11
- JVM源码仅展示关键内容,另附Open JDK链接
- 文末附Java方法使用Demo的Gitee地址
Thread.start和Thread.run
上一篇中我们已经知道,Thread.run实际上是来自Runnable
接口,直接调用并不会启动新线程,只会在主线程中运行。
Thread.start方法中调用的Thread.start0方法是真正承载了创建线程,调用Thread.run方法的能力。
其实到这里已经回答了它们之间的区别,接下来我们一起来看底层是如何实现的。
Tips:有面向对象编程语言基础的,看懂JVM源码对你来说并不困难。
首先是thread.c文件,该文件为Java中Thread类注册了native方法。
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
{"yield", "()V", (void *)&JVM_Yield},
{"sleep", "(J)V", (void *)&JVM_Sleep},
{"interrupt0", "()V", (void *)&JVM_Interrupt}
};
Tips:native方法是Java Native Interface,简称JNI。
第一眼就可以看到start0
对应的JVM方法JVM_StartThread
,实现是在jvm.cpp中:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
throw_illegal_thread_state = true;
} else {
// 创建虚拟机层面的线程
native_thread = new JavaThread(&thread_entry, sz);
}
Thread::start(native_thread);
JVM_END
接着来看new JavaThread
做了什么,在thread.cpp中:
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
os::create_thread(this, thr_type, stack_sz);
}
os::create_thread
创建了操作系统层面的线程。这和上一篇中得到的结论是一致的,Java中的Thread.start0完成了操作系统层面线程的创建和启动。
个人认为Thread.run
和Thread.start
是没什么可比性的。如果被问到这个问题,要么是面试官懒,网上随便找找就来问,要么是技术水平确实一般。
Tips:
- Thread::start方法在thread.cpp中
- os::create_thread方法在os_linux.cpp中,注意操作系统的区别
- os::pd_start_thread方法在os_linux.cpp中,注意操作系统的区别
- os::start_thread方法在os.cpp中
Thread.sleep和Object.wait
接下来看两个可以放在一起比较的方法:
- Object.wait
- Thread.sleep
很明显的区别是,它们并不在同一个类中定义,其次方法名上也能看出些许差别,“等待”和“睡眠”。
Object.wait
Java在Object类中,提供了2个wait方法的重载,不过最终都是调用JNI方法:
public final native void wait(long timeoutMillis) throws InterruptedException;
方法声明中我们能得知该方法的作用--使线程暂停指定的时间。
接着我们来看Object.wait
的方法注释:
Causes the current thread to wait until it is awakened, typically by being notified or interrupted, or until a certain amount of real time has elapsed.
使当前线程阻塞,直到主动唤醒或者超过指定时间。清晰的说明了Object.wait
的功能,另外也提示了如何唤醒线程:
- Object.notify
- Object.notifyAll
有了之前的经验,很容易想到Object.wait
方法是在Object.c中注册的。我们找到它在jvm.cpp中的实现:
JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
ObjectSynchronizer::wait(obj, ms, CHECK);
JVM_END
接着是ObjectSynchronizer::wait
,在synchronizer.cpp中:
int ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj(), inflate_cause_wait);
monitor->wait(millis, true, THREAD);
return dtrace_waited_probe(monitor, obj, THREAD);
}
获取ObjectMonitor对象时,调用了ObjectSynchronizer::inflate
方法,inflate翻译过来是膨胀的意思,是锁膨胀的过程。实际上,在未展示的代码中,还有偏向锁的过程,不过这些不是这部分的重点。
然后调用ObjectMonitor.wait,这个方法有225行,只看想要的部分:
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
// 获取当前线程
Thread * const Self = THREAD;
// 添加到等待队列中
AddWaiter(&node);
// 退出监视器
exit(true, Self);
// 对等待时间的处理
if (millis <= 0) {
Self->_ParkEvent->park();
} else {
ret = Self->_ParkEvent->park(millis);
}
}
答案已经呼之欲出了,ObjectMonitor.wait中退出了监视器,在Java层面就是Object.wait方法会释放监视器锁。
对不同等待时间的处理也需要关注一下,millis <= 0
的情况下,执行的是Self->_ParkEvent->park()
,除非主动唤醒,否则线程永远停在这里。在Java层面看,执行object.wait(0)会使当前线程永久阻塞。
既然都到这了,就多说一句,ObjectMonitor.exit中有几行关键代码,是synchronized
特性实现的关键:
void ObjectMonitor::exit(bool not_suspended, TRAPS) {
for (;;) {
if (Knob_ExitPolicy == 0) {
OrderAccess::release_store(&_owner, (void*)NULL);
OrderAccess::storeload();
}
}
}
这些内容我们提前混个眼熟,后面在synchronized
中详细解释。
我们来思考两个问题:
- 为什么Object.wait必须要在synchronized中调用?
- 为什么wait方法设计在Object类中,而不是Thread类中?
首先,我们已经知道Object.wait
的底层实现中,要释放监视器锁,释放的前提是什么?要先拥有监视器锁。那么在synchronized
中调用Object.wait
就很容易理解了。
其次,锁住的是什么?是对象,从来都不是执行线程(Thread实例是线程对象,不是执行线程)。因此涉及到监视器锁操作的方法是不是放到Object中更合适呢?
最后,如果你仔细阅读过Object.wait
所有重载方法注释的话,你会发现一个词:spurious wakeup(虚假唤醒)。
这是没有主动notify/notifyAll,或者被动中断,超时的情况下就唤醒处于WAITING
状态的线程。因此Java也建议你在循环中调用Object.wait
:
synchronized (obj) {
while (<condition does not hold> and <timeout not exceeded>) {
long timeoutMillis = ... ; // recompute timeout values
int nanos = ... ;
obj.wait(timeoutMillis, nanos);
}
...// Perform action appropriate to condition or timeout
}
简单解释下虚假唤醒产生的原因,我们已经知道Object.wait
最终是通过Self->_ParkEvent->park()
或Self->_ParkEvent->park(millis)
实现线程暂停的,其调用的park方法位于os_posix.cpp中:
void os::PlatformEvent::park() {
status = pthread_cond_wait(_cond, _mutex);
}
int os::PlatformEvent::park(jlong millis) {
status = pthread_cond_timedwait(_cond, _mutex, &abst);
}
pthread_cond_wait
和pthread_cond_timedwait
是Linux对POSIX的实现,知道其作用即可,就不继续深入了。
我们很容易联想到,Object.notify
的底层实现是调用os::PlatformEvent::unpark
方法完成的。不出所料,从Object.c到ObjectMonitor.cpp,最后会发现该方法包含在os_posix.cpp中:
void os::PlatformEvent::unpark() {
status = pthread_cond_signal(_cond);
}
同样的,pthread_cond_signal
也是Linux对POSIX的实现。Linux man page中对其的解释是:
The pthread_cond_broadcast() function shall unblock all threads currently blocked on the specified condition variable cond.
The pthread_cond_signal_() function shall unblock at least one of the threads that are blocked on the specified condition variable cond (if any threads are blocked on cond).
其中第二段是关键,即pthread_cond_signal
会唤醒至少一个阻塞在指定条件上的线程。也就是说,调用Object.notify
可能会唤醒不止一个符合条件的线程。