Thread
的yield()
、sleep()
方法、Object
的wait()
方法和Unsafe
的park()
方法,都能够阻塞当前线程,让出CPU执行权,那么它们底层实现上又有什么区别呢?本文将从JVM源码层面分别解析这几个方法的实现逻辑。
Thread::yield
是一个JNI方法
1 | public static native void yield(); |
hotspot的JNI的实现入口一般都是在jvm.cpp
中
1 | JVM_ENTRY(void, JVM_Yield(JNIEnv *env, jclass threadClass)) |
最终会调用sched_yield()
1 | void os::yield() { |
这是一个linux的系统调用,下面是相关的内核代码
1 | SYSCALL_DEFINE0(sched_yield) |
这里就比较清晰了,首先调用当前任务(线程)对应调度类的yield_task()
函数,然后调用schedule()
函数执行一次重新调度,相当于为当前CPU选择下一个要执行的任务。
对于普通线程来说,对应的调度队列是cfs_rq
,对应的调度类是cfs_sched_class
,对应的yield_task()
函数是yield_task_fair()
1 | static void yield_task_fair(struct rq *rq) |
其实这里相当于只是把当前线程标记成了skip
接下来进行一次__schedule()
的调用,通俗地讲,就是从当前CPU的运行队列中取出一个任务执行,并将前一个任务放回队列中去。
对于普通任务来说,体现函数pick_next_entity
中,这个函数从cfs_rq
的红黑树队列取出下一个任务的调度实体
1 | pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr) |
此时当前任务其实被标记为skip了,所以即使当前任务的vruntime比红黑树(通过vruntime排序,它是task的加权运行时间,权重与task优先级有关)最小节点低,也会返回最小节点,作为下一次要执行的任务。也就是说,只要红黑树不是空的,当前线程就会让出CPU。
然后,当前任务当然还要添加到队列里面去等待下一次调度啊。
1 | static struct task_struct * pick_next_task_fair(...) |
Thread::sleep
也是一个JNI方法
1 | public static native void sleep(long millis) throws InterruptedException; |
sleep
的入口如下,可以看出来,如果参数是0的话,可以转换成yield
1 | JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis)) |
调用了os::sleep
函数(JVM实现的os,并不是操作系统的sleep),linux平台的实现代码如下
1 | int os::sleep(Thread* thread, jlong millis, bool interruptible) { |
最终是调用ParkEvent
的park
函数,实现如下
1 | int os::PlatformEvent::park(jlong millis) { |
熟悉的味道吧,上面是一个典型的Mesa Monitor条件等待代码了,其中os::Linux::safe_cond_timedwait
的代码比较简单,就是调用了pthread_cond_timedwait
函数
1 | int os::Linux::safe_cond_timedwait(pthread_cond_t *_cond, pthread_mutex_t *_mutex, const struct timespec *_abstime) |
Object::wait
也是JNI方法
1 | public final native void wait(long timeout) throws InterruptedException; |
对应的入口是JVM_MonitorWait
1 | JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms)) |
显而易见,Object::wait
是配合synchronized
使用的,对应的代码是在synchronizer.cpp
中,其中的wait
实现代码如下
1 | void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) { |
首先撤销偏向锁,然后膨胀为重量级锁,再调用ObjectMonitor
的wait
函数。
忽略ObjectMonitor
复杂的实现机制,我们只看关键的地方,如下所示
1 | void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { |
到这里,是不是明白为啥wait
要写在synchronized
块里面了吧,面试官再问你sleep和wait的区别
,是不是就可以开㨃了啊?
最终也是调用了os::PlatformEvent::park
函数,与sleep
的实现方式一致。
Unsafe::park
同样也是JNI
1 | public native void park(boolean isAbsolute, long time); |
Unsafe
类比较特殊,它的native方法的入口在unsafe.cpp
里面
1 | UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time)) |
和sleep
、wait
不同的是,这里调用的是Parker
的park
函数,而不是os::PlatformEvent::park
了
1 | void Parker::park(bool isAbsolute, jlong time) { |
还是熟悉的味道,最终仍然是依赖于pthread_cond_timedwait
来阻塞线程,与sleep
不同的是,如果参数是0,park
会一直阻塞。
总结
yield
相当于进行了一次主动调度,当前线程放弃CPU使用权,重新进入CPU的运行队列,等待下一次调度。
sleep
、wait
和park
最终都是借助于pthread_cond_timedwait
实现阻塞,其中wait
比较特殊的是,需要结合ObjectMonitor
使用。