我创建了1000个线程,每个线程大约需要10秒才能完成.在这10秒内,线程不会Hibernate ,而是执行简单的数学运算. 然而,在我的PC(英特尔i7 6核)上,该程序仍然在20秒内完成.我不明白是怎么回事.我预计这至少需要大约1000/6=167秒.知道这是怎么回事吗?

public class test
{
   public static void main(String args[]) throws Exception
   {
       long startTime = System.currentTimeMillis();
       Thread[] threads = new Thread[1000];
       for(int i = 0; i<1000; i++){
           threads[i] = new Thread(()->doSomething(10));
           threads[i].start();
       }
       for(Thread t : threads){ 
           t.join(); 
       }
       long endTime = System.currentTimeMillis();
       System.out.println("Time taken "+(endTime - startTime)/1000);
   }
   
   public static int doSomething(int seconds){
    long st = System.currentTimeMillis();
    long usageTimeInMillis = seconds*1000L;
    long startTime = System.currentTimeMillis();
    int i = 0;
    while ((System.currentTimeMillis() - startTime) < usageTimeInMillis) { i++; } 
    long lt = System.currentTimeMillis();
    System.out.println("Done "+Thread.currentThread().getId()+" in "+(lt-st)/1000+" seconds ");
    return i;
   }
}

以下是部分输出:

Done 48 in 10 seconds 
Done 36 in 10 seconds 
Done 597 in 10 seconds 
...
Done 206 in 10 seconds 
Done 217 in 10 seconds 
....
Done 462 in 10 seconds 
Time taken 17

输出显示每个线程确实运行了大约10秒.那为什么1000个线程在17秒内就完成了?

推荐答案

commented by Maurice Perry岁时,你设计了一个需要10秒执行的任务,但这是时钟上的10秒时间,而不是CPU活动的10秒.

您重复循环,判断每个循环的当前时间.你可以拨打System.currentTimeMillis()查时间.您的意图是让CPU核心在整个10秒内都处于忙碌状态.但事实并非如此.

在Java中管理平台线程的主机OS根据自己 Select 的时间和持续时间调度一个线程执行.您的Java平台线程可能会随时被该主机操作系统暂停.因此,在一个极端的例子中,您的代码可能只判断当前时间一次,结果是2024-01-23T00:00:22.123Z.然后,主机操作系统可能会挂起您的线程.您的线程在几秒钟内什么都不做.最终,主机操作系统会调度该线程以供进一步执行.代码第二次判断时间时,结果是2024-01-23T00:00:34.567Z.因此,超过12秒过go 了,但您的代码只短暂地运行了两次,只是判断了两次当前时间.

这个例子有点极端,因为主机操作系统通常不会让线程在不执行的情况下运行12秒.但是,如果您的主机的CPU严重过载,例如运行1000个Java线程,则确实可能发生如此长的线程挂起.

因此,您所体验到的行为是一种功能,而不是错误.在系统时钟上等待10秒到期的1000个任务实际上总共需要大约10秒.

在我的MacBook Pro、16英寸、2021、Apple M1 Pro、10核(8性能和2效率)、16 GB RAM、MacOS Sonoma 14.3.1、IntelliJ IntelliJ Idea 2023.3.4(旗舰版)中的Java 21.0.1上运行您的准确代码需要20秒.

然后我修改了你的代码.我显示了在每个任务中执行的循环的数量.

package work.basil.example.threading;


import java.time.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

public class BusyBusy
{
    public static void main ( String args[] ) throws Exception
    {
        System.out.println ( "Demo start at " + Instant.now ( ) );
        final long demoStartNanos = System.nanoTime ( );

        final int countTasks = 1_000;
        try (
                ExecutorService executorService = Executors.newFixedThreadPool ( countTasks ) ; // We want one task per thread. So one thousand tasks means one thousand threads.
        )
        {
            IntStream
                    .rangeClosed ( 1 , countTasks )
                    .mapToObj ( TenSecondTask :: new )
                    .forEach ( executorService :: submit );
        }

        Duration demoDuration = Duration.ofNanos ( System.nanoTime ( ) - demoStartNanos );
        System.out.println ( "Demo end at " + Instant.now ( ) + ". demoDuration = " + demoDuration );
    }
}

class TenSecondTask implements Runnable
{
    private final int sequenceNumber;

    public TenSecondTask ( final int sequenceNumber )
    {
        this.sequenceNumber = sequenceNumber;
    }

    @Override
    public void run ( )
    {
        final long tenSecondsInNanos = Duration.ofSeconds ( 10 ).toNanos ( );
        final long taskStartNanos = System.nanoTime ( );
        long countLoops = 0;
        while (
                ( System.nanoTime ( ) - taskStartNanos ) < tenSecondsInNanos
        )
        {
            countLoops++;
        }
        System.out.println ( "Task # " + this.sequenceNumber + " in thread ID " + Thread.currentThread ( ).threadId ( ) + " done at " + Instant.now ( ) + ". Loops: " + String.format ( "%,d" , countLoops ) );
    }
}

我的结果显示范围很广,从10万到700多万.总运行时间约为20秒,与您的代码相同.

Demo start at 2024-02-15T09:24:13.773544Z
Task # 6 in thread ID 26 done at 2024-02-15T09:24:23.779903Z. Loops: 328,186
Task # 11 in thread ID 31 done at 2024-02-15T09:24:23.812401Z. Loops: 349,456
Task # 13 in thread ID 33 done at 2024-02-15T09:24:23.839549Z. Loops: 356,229
Task # 20 in thread ID 40 done at 2024-02-15T09:24:23.883941Z. Loops: 379,757
…
Task # 991 in thread ID 1019 done at 2024-02-15T09:24:33.561808Z. Loops: 981,788
Task # 992 in thread ID 1020 done at 2024-02-15T09:24:33.566229Z. Loops: 1,034,678
Task # 993 in thread ID 1021 done at 2024-02-15T09:24:33.572045Z. Loops: 1,172,157
Task # 994 in thread ID 1022 done at 2024-02-15T09:24:33.576395Z. Loops: 1,067,262
Task # 995 in thread ID 1023 done at 2024-02-15T09:24:33.582312Z. Loops: 1,034,725
Task # 996 in thread ID 1024 done at 2024-02-15T09:24:33.586528Z. Loops: 1,134,316
Task # 997 in thread ID 1025 done at 2024-02-15T09:24:33.592593Z. Loops: 1,112,738
Task # 998 in thread ID 1026 done at 2024-02-15T09:24:34.158249Z. Loops: 6,455,185
Task # 999 in thread ID 1027 done at 2024-02-15T09:24:34.158381Z. Loops: 6,484,593
Task # 1000 in thread ID 1028 done at 2024-02-15T09:24:34.168723Z. Loops: 7,411,728
Demo end at 2024-02-15T09:24:34.168958Z. demoDuration = PT20.392301916S

顺便说一句,System.currentTimeMillis()在几年前就被Instant.now()取代了.在基于OpenJDK个代码库的实现上,调用Instant.now以微秒的分辨率捕捉当前时刻,而不是在Java 9+中仅以毫秒为单位.

Java相关问答推荐

具有默认分支的JUnit代码覆盖率切换声明

Java Streams在矩阵遍历中的性能影响

BiPredicate和如何使用它

S的字符串表示是双重精确的吗?

GSON期间的Java类型擦除

无法使用Java&;TestContainers获取AWS SQS队列的属性

解释左移在Java中的工作原理

如何在Application.yaml中连接字符串?

二进制数据的未知编码/序列化

Quarkus:运行时出现EnumConstantNotPresentException

每次我需要时创建和关闭数据库连接会有什么效果吗?

JavaFX标签中的奇怪字符

Java类型推断:为什么要编译它?

让标签占用JavaFX中HBox的所有可用空间

为什么Instant没有从UTC转换为PostgreSQL的时区?

ControlsFX RangeSlider在方向垂直时滞后

使用StringBuilder和append方法创建字符串时Java字符串内部方法的问题

转换为JSON字符串时,日期按天递减-Java

OpenAPI Maven插件生成错误的Java接口名称

如何在 WebSphere Application Server 内的托管线程上运行 BatchEE 作业(job)?