为什么线程10000 start()调用比10000 run()调用花费更多时间?

我在线程上做了一个hello world,我创建了一个简单的线程,使用run()调用(它只是一个普通的方法调用)和一个重复的线程,使用start()调用生成另一个线程来处理,但是, start()调用所花费的时间多于run()调用所用的时间,而这些调用不是线程调用,为什么会这样呢?

开始通话时间:00:00:08:300

  long time = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { Thread thread = new Thread(new Car()); thread.setName(Integer.toString(i)); thread.start(); } long completedIn = System.currentTimeMillis() - time; System.out.println(DurationFormatUtils.formatDuration(completedIn, "HH:mm:ss:SS")); 

运行通话时间:00:00:01:366

  long time = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { Thread thread = new Thread(new Car()); thread.setName(Integer.toString(i)); thread.run(); } long completedIn = System.currentTimeMillis() - time; System.out.println(DurationFormatUtils.formatDuration(completedIn, "HH:mm:ss:SS")); 

从评论到接受的回复:“你对此有什么建议吗?”

是的,不要直接使用线程。 从java 5开始,我们有了java.util.concurrent框架,它允许简化线程和任务管理。

创建一个线程是昂贵的。 即使在线程运行您想要的任务之前,它也必须被创建,这是一个非常漫长的过程。 出于这个原因,我们有线程池的概念。 您不必每次要执行并发任务时创建新线程,而是创建您需要的线程,以便它们准备就绪,并且您也可以在需要时向它们发送任务。 一个线程池作为第二个adventage。 任务完成后,线程不会被销毁,但会保持活动状态以运行下一个任务,因此在初始化时,线程创建成本只会发生一次。

那么你如何使用这些概念呢?

首先使用线程池创建一个执行程序。 为了保持简单,我们创建了一个包含100个线程的线程池(因为你想模拟100个并发调用的加载):

 ExecutorService pool = Executors.newFixedThreadPool(100); 

然后你提交你的任务:

 long time = System.currentTimeMillis(); for (int i=0; i<100000; i++) { pool.execute(new Car()); } 

而且重要的是,在停止程序之前,您将等待所有任务完成!

 pool.shutdown(); //Do no longer accept new tasks. pool.awaitTermination(1, TimeUnit.HOURS); //Wait for up to one hour for all tasks to finish. long completedIn = System.currentTimeMillis() - time; System.out.println(DurationFormatUtils.formatDuration(completedIn, "HH:mm:ss:SS")); 

在您的线程代码中,您没有等待线程完成其工作,实际上您已经确定了线程创建时间,而不是任务运行时间。

代码做什么?

executor.execute方法在线程内执行提供的任务。 这里需要100个线程中的一个,让它执行任务。

如果有超过100个任务会怎样?

使用100个线程,您无法运行超过100个并发任务。 其他任务将排队,直到一个任务完成,因此一个线程可用于执行它。 这是确保您不会创建太multithreading和OutOfMemory或其他令人讨厌的事情不会发生的好方法。

你应该使用多少个线程?

这取决于您要执行的任务类型。

如果它就像一个Web服务器,你主要是IO绑定等待数据库获取数据然后为网络发送响应,你的线程将主要等待。 因此,即使一个CPU也可以从十几个,甚至一百个线程中受益。 即使更multithreading开始减慢整个应用程序的速度,它也允许处理用户请求而不是让它等待,或者只是拒绝响应。

如果您的任务是CPU绑定的,那么每个CPU核心都需要一个任务,以便最大限度地利用硬件,同时限制上下文切换的开销。 使用4核超线程CPU,您最多可以使用8个并发线程。

这个响应只是一个简短的介绍...您将通过查看java.util.concurrent包并阅读一些教程来了解更多信息。

start实际创建一个新线程(繁重的操作),而run调用当前线程上的线程对象的方法run (简单方法调用 – 轻量操作)

从关于start的线程文档:

导致此线程开始执行; Java虚拟机调用此线程的run方法。 结果是两个线程同时运行:当前线程(从调用start方法返回)和另一个线程(执行其run方法)。

你永远不应该直接调用run 。 你在那里做的是创建一堆Thread对象,但你实际上从未创建过新的线程; 你只需在主线程上运行代码(因为你直接调用run )。

创建100,000个线程在大多数当前的计算机上都不会运行良好(我正在抛弃高端价值数百万美元的计算机)。 只要有足够多的线程支持CPU容量,就会开始导致上下文切换。 因此,如果您有一个四核系统,运行四个以上的线程实际上会降低程序速度(模数I / O操作以及CPU无论如何都会空闲)。

当你启动一堆线程而你只有一个或两个核心时,上下文切换和运行任务所花费的时间很容易超过顺序运行代码所花费的时间,一个接一个的任务。

start方法就是神奇的地方。 它产生了一个新的线程,这需要时间:

  • 创建线程
  • 开始线程
  • 管理线程生命周期

最好的解释将在java.lang.Thread的源代码中找到。

run方法是线程启动的方法,但它只是Runnable接口的一个简单方法。

Thread.start()方法将创建一个新的线程(执行,一个真正的线程),它将执行线程实例的run()方法。

可以在线程实例上调用run()方法,因为有两种方法可以定义执行的执行线程。

  • 旧的:扩展Thread的run方法。 由于某些原因(包括耦合),不建议再使用它。

  • 新的:将Runnable(提供run()方法)传递给线程的构造函数。

请注意,如果您创建一个带有Runnable(目标)的线程并且还覆盖线程的run()方法,您可以理解您当前正在提供2个代码来执行,但哪个代码将被执行? 如果你看一下Thread的默认run()实现,那很容易找到:

 public void run() { if (target != null) { target.run(); } } 

如果覆盖该方法,则会绕过提供的Runnable目标,除非您在覆盖上调用它。

正如我所说,有两种方法可以启动一个线程。 我想当他们设计“新方法”时,他们必须在Thread类中保留run()方法以获得后向兼容性问题,但是如果他们可以回到过去,他们可能不会让Thread类实现Runnable接口以便那里关于如何启动线程会更少混淆…