“thread.sleep”与project loom for java中的虚拟线程(光纤)不同吗

z0qdvdin  于 2021-06-29  发布在  Java
关注(0)|答案(3)|浏览(385)

我用 Thread.sleep 在试验或演示java代码的并发性时。通过睡觉,我假装一些处理工作正在进行,这将需要一段时间。
我想知道如何在织布机项目下做这件事。
在项目织机技术与虚拟线程(纤维),我们可以使用 Thread.sleep 以同样的方式?
睡虚拟线程和睡平台/内核线程有什么不同或值得注意的地方吗?
为了教育自己,我和甲骨文公司的罗恩·普雷斯勒一起观看了一些2020年后期的视频,介绍了ProjectLoom技术(这里,这里)。我不记得他讲过睡线的问题,虽然这很有启发性。

jucafojl

jucafojl1#

在带有虚拟线程(光纤)的项目绝缘线束技术下,我们能以同样的方式使用thread.sleep吗?
看来是这样。我指的是openjdkwiki上的页面,它解决了loom中的阻塞操作。它列出 Thread.sleep() 在对虚拟线程友好的操作中,这意味着
当没有固定时,当操作阻塞时,它们将释放底层的载体线程来做其他工作。
你继续问,
睡虚拟线程和睡平台/内核线程有什么不同或值得注意的地方吗?
文件很少,而且不清楚实际存在的任何差异是否是故意的。然而,我倾向于认为睡眠虚拟线程的目标是使其语义尽可能接近睡眠普通线程的语义。我怀疑一个足够聪明的程序会有办法加以区分,但是如果有任何差异上升到“值得注意”的水平,那么我预计它们将被视为bug。我在一定程度上基于推断,但我也让您参考java.net上的loom文档的状态,它列出了
虚拟线程是运行时、调试器和探查器中代码中的线程。

不需要更改语言。
(重点补充)

nxagd54h

nxagd54h2#

看看源代码,当你调用 sleep(...) 在虚拟线程上,它由jvm的虚拟线程调度器处理;i、 不直接执行系统调用,也不阻塞本机线程。
所以:
在项目织机技术与虚拟线程(纤维),我们可以使用 Thread.sleep 以同样的方式?
对。
睡虚拟线程和睡平台/内核线程有什么不同或值得注意的地方吗?
休眠虚拟线程的处理方式与您期望的虚拟线程的行为方式相同。性能将不同于内核线程,但行为设计为透明的应用程序代码。。。这并不是对线程调度程序的行为做出毫无根据的假设。
无论如何,javadocs Thread.sleep(...) 在loom中,目前没有提到内核线程和虚拟线程之间的任何区别。

iqxoj9l9

iqxoj9l93#

约翰·博林格的答案和斯蒂芬·c的答案都是正确的,而且内容丰富。我想我应该添加一个代码示例来说明:
虚拟线程和平台/内核线程如何相互尊重 Thread.sleep .
项目织机技术可能带来的惊人性能提升。

基准代码

让我们简单地写一个循环。在每个循环中,我们示例化一个 Runnable 执行任务,并将该任务提交给执行器服务。我们的任务是:做一些简单的数学,从 long 退回人 System.nanoTime . 最后,我们将这个数字打印到控制台。
但诀窍是,在计算之前,我们让执行该任务的线程休眠。由于每次睡眠的最初12秒,我们应该看到没有出现在控制台上,直到至少12秒的死区时间。
然后提交的任务执行它们的工作。
我们以两种方式运行它,通过启用/禁用一对注解掉的行。 ExecutorService executorService = Executors.newFixedThreadPool( 5 ) 一个传统线程池,使用6个实际内核中的5个(无超线程),在这个mac mini(2018)上,使用一个3ghz的intel core i5处理器和32gig的ram。 ExecutorService executorService = Executors.newVirtualThreadExecutor() 在这个特殊的早期访问java16构建中,由projectloom提供的新虚拟线程(光纤)支持的执行器服务。

package work.basil.example;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TooFast
{
    public static void main ( String[] args )
    {
        TooFast app = new TooFast();
        app.demo();
    }

    private void demo ( )
    {
        System.out.println( "INFO - starting `demo`. " + Instant.now() );

        long start = System.nanoTime();
        try (
                // 5 of 6 real cores, no hyper-threading.
                ExecutorService executorService = Executors.newFixedThreadPool( 5 ) ;
                //ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
        )
        {
            Duration sleep = Duration.ofSeconds( 12 );
            int limit = 100;
            for ( int i = 0 ; i < limit ; i++ )
            {
                executorService.submit(
                        new Runnable()
                        {
                            @Override
                            public void run ( )
                            {
                                try {Thread.sleep( sleep );} catch ( InterruptedException e ) {e.printStackTrace();}
                                long x = ( System.nanoTime() - 42 );
                                System.out.println( "x = " + x );
                            }
                        }
                );
            }
        }
        // With Project Loom, the flow-of-control  blocks here until all submitted tasks have finished.
        Duration demoElapsed = Duration.ofNanos( System.nanoTime() - start );

        System.out.println( "INFO - demo took " + demoElapsed + " ending at " + Instant.now() );
    }
}

结果

结果令人吃惊。
首先,在这两种情况下,在任何控制台活动之前,我们都会看到超过12秒的延迟。所以我们知道 Thread.sleep 是由平台/内核线程和虚拟线程真正执行的。
其次,虚拟线程只需几秒钟就可以完成所有任务,而传统线程只需几分钟、几小时或几天。
有100个任务:
常规螺纹需要4分钟(pt4m0.079402569)。
虚拟线程只需超过12秒(pt12.087101159)。
有1000个任务:
常规螺纹需要40分钟(pt40m0.667724055s)。
(这很有道理:1000*12/5/60=40)
虚拟线程需要12秒(pt12.177761325s)。

有1000000个任务:
传统的线程需要…好吧,几天。
)其实我没有等。在早期版本的代码中,我曾经历过一次长达29小时的50万次循环。)
虚拟线程需要28秒(pt28.043056938)。
(如果我们减去12秒的休眠时间,那么在剩下的16秒内执行所有工作的100万个线程就可以立即执行每秒62500个线程任务。)

结论

使用传统的线程,我们可以看到控制台上突然出现了几行重复的突发事件。因此,我们可以看到平台/内核线程实际上是如何在内核上被阻塞的,因为它们等待12秒 Thread.sleep 过期。然后所有五个线程都在大约同一时刻醒来,每12秒在大约同一时刻开始,同时进行计算并写入控制台。这一行为得到了证实,因为我们在ActivityMonitor应用程序中很少看到cpu内核的使用。
旁白:我假设主机操作系统注意到我们的java线程实际上在忙着什么都不做,然后使用它的cpu调度程序在被阻止时挂起我们的java线程,让其他进程(如其他应用程序)使用cpu内核。但如果是这样,这对我们的jvm是透明的。从jvm的Angular 来看,休眠java线程在整个nap期间占用cpu。
对于虚拟线程,我们可以看到截然不同的行为。projectloom的设计使得当一个虚拟线程阻塞时,jvm将该虚拟线程移出平台/内核线程,并放置另一个虚拟线程。在jvm内交换线程比交换平台/内核线程便宜得多。承载这些虚拟线程的平台/内核线程可以保持忙碌,而不是等待每个块通过。
有关更多信息,请参阅甲骨文织布机项目的罗恩·普雷斯勒(ron pressler)最近(2020年末)发表的任何一篇演讲,以及他在2020-05年发表的论文《织布机现状》。这种快速交换被阻塞的虚拟线程的行为非常有效,以至于cpu可以一直处于繁忙状态。我们可以在活动监视器应用程序中确认此效果。下面是活动监视器使用虚拟线程运行百万任务的屏幕截图。请注意,在所有一百万个线程完成12秒的休眠后,cpu核心实际上是100%忙碌的。

因此,所有的工作都是立即有效地完成的,因为所有一百万个线程同时进行12秒的午睡,而平台/内核线程则是以5个为一组连续进行午睡。在上面的屏幕截图中,我们看到百万个任务的工作是如何在几秒钟内一次完成的,而平台/内核线程的工作量是相同的,但要在几天内完成。
请注意,只有当任务经常被阻塞时,才会出现这种显著的性能提高。如果使用cpu绑定的任务,比如视频编码,那么应该使用平台/内核线程而不是虚拟线程。大多数商业应用程序都会遇到很多阻塞,比如等待对文件系统、数据库、其他外部服务或网络的调用来访问远程服务。虚拟线程在这种经常被阻塞的工作负载中大放异彩。

相关问题