当前位置: 首页 > 科技观察

疯了,还有这么神奇的Thread.sleep(0)的写法?

时间:2023-03-17 15:03:44 科技观察

前言最近在网上看到一段代码,感觉很迷茫。他在代码中使用Thread.sleep(0)让线程休眠0秒。具体代码如下。inti=0;while(i<10000000){//业务逻辑//防止长时间gcif(i%3000==0){try{Thread.睡觉(0);}catch(InterruptedExceptione){e.打印堆栈跟踪();}}}sleep0秒,不就是没睡吗?我的第一反应是这段代码没有用,但看到他的评论激起了我的兴趣。经过一些研究,一段看似无用的代码实际上可以提供很多东西。探索分析为了找到原因,先看sleep方法的javadoc,如下:使当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,以系统的精度和准确度为准计时器和调度程序。该线程不会失去任何监视器的所有权。显然没有得到正确答案,最后问作者说使用Thread.sleep(0)可以暂时释放CPU时间线。时间片周期调度算法在操作系统中,CPU有很多竞争策略。Unix系统使用时间片循环调度算法。在这个算法中,所有进程都被分组到一个队列中。操作系统依次为每个进程分配一定的时间,即允许进程运行的时间。如果时间片结束时进程还在运行,则将CPU拿走分配给另一个进程,如果进程在时间片内阻塞或结束,则立即切换CPU。调度程序所要做的就是维护一个就绪进程表。当一个进程用完时间片时,它被移到队列的末尾。上面的代码中有一个死循环。作者希望一直用一个线程来处理业务逻辑。如果Thread.sleep(0)不主动放弃CPU时间片,线程资源就会一直被占用。众所周知,GC线程具有低优先级,因此Thread.sleep(0)用于帮助GC线程尝试竞争CPU时间片。但是为什么作者说可以防止长时间GC呢?这就讲到了JVM的垃圾回收原理。GC的安全点以HotSpot虚拟机为例,JVM不会在代码指令流的任何一点停顿开始垃圾回收,而是强制执行必须到达安全点才会停顿。换句话说,JVM在到达安全点之前不会GCSTOPTHEWORLD。JVM在一些循环跳转和方法调用上设置安全点。不过,为了避免安全点过多带来的沉重负担,HotSpot虚拟机还有针对循环的优化措施。如果循环次数少,执行时间不宜过长。因此,默认情况下不会将使用int或更小数据类型作为索引值的循环放在安全点中。这样的循环称为可数循环。相应地,使用long或更大范围的数据类型作为索引值的循环称为未计数循环,将被放在安全点。但是,我们这里恰好有一个可数循环,所以我们的代码不会放在安全点。因此,GC线程必须等到线程执行完毕,才能执行到最近的安全点。但是如果你使用Thread.sleep(0),你可以在你的代码中放置一个安全点。我们可以看看HotSpot的safepoint.cpp源码中的注释,做一下说明。//开始将系统带到安全点的过程。//Java线程可以处于几种不同的状态,并且//由不同的机制停止////1.运行解释//interpeter调度表更改为强制它//检查字节码之间的安全点条件。//2.在本机代码中运行//当从本机代码返回时,Java线程必须检查//安全点_state以确定我们是否必须阻塞。如果//VM线程在本机中看到一个Java线程,它//不会等待此线程阻塞。安全点状态和Java//线程状态的内存//写入和读取顺序很关键。为了保证//内存写入相对于彼此序列化,//VM线程发出内存屏障指令//(在MP系统上)。为了避免为每个进行本机调用的Java线程发出//内存屏障的开销,每个Java//线程在更改//线程状态后执行对单个内存页的写入。VM线程执行一系列//mprotectOS调用,这会强制所有以前的所有写入//Java线程进行序列化。这是在//os::serialize_thread_states()调用中完成的。事实证明,这比在每次调用本机代码时//执行membar指令要高效得多。//3.运行编译代码//编译代码读取全局(安全点轮询)页面,该页面//设置为故障如果我们试图到达安全点。//4.阻塞//被阻塞的线程将不允许从//阻塞条件返回,直到安全点操作完成。//5.在VM或Transitioning之间states//如果Java线程当前正在VM中运行或在//状态之间转换,则安全点代码将等待线程//在尝试转换时阻塞自身进入一个新的状态。可以看到上面第二点Runninginnativecode,Thread.sleep(longmillis)是native方法,断定Thread.sleep(0)不是无用代码。sleep方法可用于在java代码中放置一个安全点。可以提前在长周期内触发GC,避免GC线程长时间等待,从而达到延长GC时间的目的。