Thread.sleep(0)的奇怪现象

Thread.sleep(0)的奇怪现象
逐暗者背景
先来看这么一段代码
1 | public class Test { |
这段代码启动了2个线程,然后运行了很多次计数器加1操作,主线程等待1000毫秒后就打印。看上去无论2个线程是否长时间运行,主线程都应该在1秒后打印num的值。
但奇怪的现象是主线程会等待2个子线程结束后再打印sleep被延长了很久。
{cat_tips_info color=””}
当然这个现象只在jdk10以下的版本会出现,从jdk10开始就不会有这个现象了。
{/cat_tips_info}
sleep(0)
现在修改一下这段代码,增加一个sleep(0)看看会有什么变化
1 | public class Test { |
一切都变得正常了,主线程1秒后就打印了数值,当然由于2个线程并未执行完,所以num值并不是2000000000 。
安全点
之所以会有这样的差异,主要是因为sleep会进入安全点。
关于安全点的描述,我们可以看看《深入理解JVM虚拟机(第三版)》的 3.4.2 小节:
在HotSpot中,JVM Stop-the-World 的暂停机制称为安全点。在安全点期间,所有运行java代码的线程都被挂起。运行native代码的线程可以继续运行,只要它们不与JVM交互(比如尝试通过JNI访问Java对象,调用Java方法或从native返回Java,这些将挂起线程直到安全点结束)。
启动安全点的原因
以下是 HotSpot JVM 启动安全点的几个原因:
- 垃圾收集暂停
- 代码去优化
- 刷新代码缓存
- 类重新定义(例如热插拔或仪表)
- 偏向锁撤销
- 各种调试操作(例如死锁检查或堆栈跟踪转储)
安全点的实现
1.安全点的检查就是在代码的某些位置插入了些安全点的检查代码
2.应用线程到达安全点时,是否需要进入安全点状态,通常只是个状态判定,比如线程是否被标记为poll armed,或者本地 local polling page 是否为脏,是否已经在block状态(已经block的,安全点未结束前,不离开block状态)等
3.应用线程如果发现需要进入安全点状态,则并把自己状态改为安全(把自己阻塞block)
4.需要所有应用线程都到达安全点安全状态(block阻塞状态),才开始做安全点的动作。
- 安全点设置的优化
在循环中如果插入过多的安全点,则会导致效率低下,所以在可以预见不会太长的循环体中不插入安全点。而使用int类型变量进行for循环就是这样的一个被认为不会太长的循环。
所以第一段代码中,之所以会出现主线程等待子线程的情况,是因为子线程在进入循环体内后没有安全点。而第二段代码中显然sleep(0)的添加增加了安全点。
如何进入安全点
其中Running in native code 中说明了,线程从native方法退回到jvm虚拟机中时需要进行安全点检查。而sleep这个方法正好就是native方法。
其他
除了采用sleep(0)来使线程进入安全点外,还能使用long代替int进行for循环。long被认为不是一个短暂的循环体,会被插入安全点检查。
另外,Java 10引入了一项更高级的叫循环切分(loop strip mining)的技术来更进一步地平衡安全点检查对吞吐量和延迟所产生的影响。就不会产生第一段代码的效果了。