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

时间:2011-07-24 08:49:24

标签: java multithreading

我在线程上做了一个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"));

6 个答案:

答案 0 :(得分:6)

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

关于start的主题文档:

  

使该线程开始执行; Java虚拟机调用   这个线程的run方法。结果就是两个线程   并发运行:当前线程(从调用返回)   到start方法)和另一个线程(执行它的运行)   法)。

答案 1 :(得分:6)

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

是的,不要直接使用线程。从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个并发任务。其他任务将排队,直到一个任务完成,因此一个线程可用于执行它。这是确保您不会创建太多线程和OutOfMemory或其他令人讨厌的事情不会发生的好方法。

您应该使用多少个帖子?

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

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

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

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

答案 2 :(得分:3)

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

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

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

  • 创建主题
  • 启动帖子
  • 管理线程生命周期

最佳解释将在java.lang.Thread的来源中找到。

答案 5 :(得分:1)

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接口以便那里关于如何启动线程会更少混淆......