无法了解Java线程的时间消耗

问题描述 投票:0回答:2

我创建了 1000 个线程,每个线程大约需要 10 秒才能完成。在这 10 秒内,线程不会休眠,而是执行简单的数学运算。 然而,该程序在我的 PC(Intel 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 秒内就完成了呢?

java multithreading java-threads
2个回答
0
投票

正如 Maurice Perry 所评论的那样,您设计了一个需要 10 秒才能执行的任务,但这是时钟上经过的 10 秒时间,而不是 CPU 活动的 10 秒。

您重复循环,检查每个循环的当前时间。您可以拨打

System.currentTimeMillis()

 查看时间。您的意图是让 CPU 核心在整个十秒内保持忙碌。但事实并非如此。

在 Java 中管理平台线程的主机操作系统安排一个线程在其自己选择的时间和持续时间内执行。您的 Java 平台线程可能会随时被该主机操作系统暂停。因此,在极端的示例情况下,您的代码可能会检查当前时间一次,结果为 2024-01-23T00:00:22.123Z。然后主机操作系统可能会挂起您的线程。您的线程在几秒钟内没有执行任何操作。最终,主机操作系统安排该线程进一步执行。代码第二次检查时间时,结果为 2024-01-23T00:00:34.567Z。这样就过去了 12 秒多,但你的代码只运行了短暂的时间,只是检查了两次当前时间。

这个例子有点极端,因为主机操作系统通常不会让线程在 12 秒内不执行。但如果你的主机的 CPU 严重超载,比如运行一千个 Java 线程,那么长时间的线程挂起确实可能发生。

因此您所经历的行为是一个功能,而不是一个错误。一千个任务在系统时钟上等待十秒到期确实总共需要大约十秒。

在我的 MacBook Pro(16 英寸,2021 年,Apple M1 Pro,16 GB RAM,macOS Sonoma 14.3.1,Java 21.0.1,IntelliJ IntelliJ IDEA 2023.3.4(终极版))上运行您的确切代码需要 20 秒。


顺便说一句,

System.currentTimeMillis()

几年前就被
Instant.now()
取代了。在基于 
OpenJDK
代码库的实现中,调用 Instant.now 以微秒的分辨率捕获当前时刻,而不仅仅是 Java 9+ 中的毫秒。


0
投票
实现此目的的一种方法是执行繁忙循环并不断检查自上次迭代以来经过了多少时间。如果它高于某个阈值,则假设发生了上下文切换,因此时间不计算在内。示例:

long passedTime=0; long i=0; long currentTime=System.nanoTime(); while (passedTime<usageTimeInMillis*1000*1000) { long nextTime = System.nanoTime(); long increase = nextTime - currentTime; if (increase < 1000*1000) { passedTime += increase; } else { passedTime += Math.min(i>0 ? passedTime/i : 0, increase); } currentTime=nextTime; i++; }
另请注意,我使用了 nanoTime 来避免由于舍入而导致不必要的误差。

上下文切换一般至少需要几毫秒,而检查当前时间、执行几次加法、减法和条件跳转则需要几十纳秒,因此可以清楚是否发生了切换.

© www.soinside.com 2019 - 2024. All rights reserved.