首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >`Thread.sleep`与项目织机中的虚拟线程(纤维)不同吗?

`Thread.sleep`与项目织机中的虚拟线程(纤维)不同吗?
EN

Stack Overflow用户
提问于 2020-12-17 00:46:20
回答 3查看 640关注 0票数 2

我在实验或演示用于Thread.sleep并发性代码时使用了并发性。通过睡觉,我假装了一些需要时间的处理工作。

我想知道在工程织机下做这件事是否可行。

  • 在虚拟线程(光纤)的工程织机技术下,我们能以同样的方式使用Thread.sleep吗?
  • 与休眠平台/内核线程相比,休眠虚拟线程有什么不同或值得注意的吗?

为了自学,我和甲骨文公司的罗恩·普雷斯勒一起看了一些2020年后期的视频,介绍了织机技术(这里这里)。虽然很有启发性,但我不记得他谈到了沉睡一条线的问题。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2020-12-17 01:38:17

  • 在带有虚拟线程(光纤)的Project技术下,我们能以同样的方式使用Thread.sleep吗?

看来是这样的。我指的是OpenJDK wiki中关于织机中的阻塞操作的页面。它列出了对虚拟线程友好的操作中的Thread.sleep(),这意味着

当未固定时,它们将释放底层的载波线程,以便在操作阻塞时执行其他工作。

你接着问,

  • 与休眠平台/内核线程相比,休眠虚拟线程有什么不同或值得注意的吗?

文档是稀疏的,并且不清楚实际存在的任何差异是否是有意的。不过,我倾向于认为,目标是使虚拟线程具有尽可能接近普通线程的语义。我怀疑会有足够聪明的程序来区分的方法,但如果有任何差异上升到“值得注意”的水平,那么我希望它们会被认为是bug。我的部分依据是推理,但我也向您介绍了织机状态文档在java.net上的应用,其中列出了它的“关键方法”

  • 虚拟线程是线程在运行时、调试器和分析器中的线程代码。

  • 不需要语言更改。

(强调后加)

票数 4
EN

Stack Overflow用户

发布于 2020-12-17 01:39:24

查看源代码,当您在虚拟线程上调用sleep(...)时,它由JVM的虚拟线程调度程序处理;也就是说,不直接执行syscall,也不阻塞本机线程。

所以:

在带有虚拟线程(光纤)的Project技术下,我们能以同样的方式使用Thread.sleep吗?

是。

与休眠平台/内核线程相比,休眠虚拟线程有什么不同或值得注意的吗?

休眠虚拟线程的处理方式与您期望的虚拟线程的行为一样。其性能将不同于内核线程,但其行为被设计为透明的应用程序代码.这并不是对线程调度程序行为做出不必要的假设。

无论如何,Loom中用于Thread.sleep(...)的javadocs目前并没有提到内核和虚拟线程之间的任何区别。

票数 3
EN

Stack Overflow用户

发布于 2020-12-24 04:47:11

约翰·博林格的答复斯蒂芬·C的回答都是正确的和信息丰富的。我想我应该添加一个代码示例来显示:

  • 虚拟线程和平台/内核线程如何尊重Thread.sleep
  • 这是惊人的性能提高,这是可能的工程织机技术。

基准代码

让我们简单地写一个循环。在每个循环中,我们实例化一个Runnable来执行一个任务,并将该任务提交给一个遗嘱执行人服务。我们的任务是:做一些简单的数学运算,从System.nanoTime返回的System.nanoTime中减去。最后,我们将该数字打印到控制台。

但诀窍是,在计算之前,我们休眠执行该任务的线程。因为每个人都睡了最初的12秒,我们应该在至少12秒的死亡时间之后才能在控制台上看到任何东西。

然后提交的任务执行他们的工作。

通过启用/禁用一对注释行,我们以两种方式运行这一点。

  • ExecutorService executorService = Executors.newFixedThreadPool( 5 ) 一个传统的线程池,使用了5个真正的核心(没有超线程)在这台Mac迷你(2018年)与3 GHz英特尔核心i5处理器和32 G内存。
  • ExecutorService executorService = Executors.newVirtualThreadExecutor() 在这个早期访问Java 16的特殊构建中,Project提供了新的虚拟线程(光纤)支持的执行器服务。
代码语言:javascript
复制
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.079402569S)。
  • 虚拟线程只需12秒(PT12.087101159S)。

有1 000项任务:

  • 常规线程耗时40分钟(PT40M0.667724055S)。 (这是合理的: 1,000 * 12 /5/ 60 = 40 )
  • 虚拟线程耗时12秒(PT12.177761325S)。

有1 000 000项任务:

  • 常规线程采用…嗯,几天。 )我并没有真正地等待。在此代码的早期版本中,我曾经历过长达29个小时的50万循环运行。)
  • 虚拟线程需要28秒(PT28.043056938S)。 (如果我们减去死掉的12秒睡眠时间,在剩下的16秒钟内执行全部工作的线程就会达到每秒62,500个线程任务。)

结论

使用常规线程,我们可以看到控制台上突然出现了几行重复的中断。因此,我们可以看到平台/内核线程在等待12秒钟的Thread.sleep到期时,实际上是如何被阻塞的。然后,所有五个线程都在大约同一时刻醒来,大约在同一时刻开始,每12秒,同时做他们的数学和写到控制台。这一行为得到证实,因为我们看到在https://en.wikipedia.org/wiki/List_of_macOS_components#Activity_Monitor应用程序中很少使用CPU核心。

顺便说一句:我假设主机操作系统注意到我们的Java线程实际上忙着什么都不做,然后使用它的CPU调度器在阻塞时挂起我们的Java线程,让其他进程(例如其他应用程序)使用CPU核心。但是如果是这样的话,这对JVM来说是透明的。从JVM的角度来看,休眠的Java线程在整个nap期间占用CPU。

对于虚拟线程,我们看到了截然不同的行为。Project的设计使得当一个虚拟线程阻塞时,JVM将该虚拟线程移出平台/内核线程,并将另一个虚拟线程放到它的位置。这种JVM内部线程交换比交换平台/内核线程便宜得多。承载这些不同虚拟线程的平台/内核线程可以保持忙碌,而不是等待每个块通过。

有关更多信息,请参见甲骨文织机项目的Ron最近(2020年末)的任何一次会谈,以及他在2020-05年度的论文https://cr.openjdk.java.net/%7Erpressler/loom/loom/sol1_part1.html?source=techstories.org。这种快速交换阻塞的虚拟线程的行为非常有效,以至于CPU可以一直处于忙碌状态。我们可以在Activity应用程序中确认这种效果。下面是活动监视器的屏幕截图,它使用虚拟线程运行百万任务。注意CPU内核在完成12秒的午睡后,实际上是100%繁忙的。

因此,所有的工作都是立即完成的,因为所有的100万个线程都在同时进行12秒钟的午睡,而平台/内核线程则是以5组的顺序进行午睡。在上面的屏幕截图中,我们看到了数百万任务的工作是如何在几秒钟内同时完成的,而平台/内核线程做同样数量的工作,但分散在几天内。

请注意,只有当任务经常被阻止时,才会出现这种戏剧性的性能提高。如果使用CPU绑定的任务,如视频编码,那么您应该使用平台/内核线程,而不是虚拟线程。大多数业务应用程序都存在很大的阻塞,例如等待对文件系统、数据库、其他外部服务或网络的调用来访问远程服务。虚拟线程在这种经常被阻塞的工作负载中发光。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65333188

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档