【问题标题】:Why is it that threads 10000 start() calls take more time than 10000 run() calls?为什么线程 10000 start() 调用比 10000 run() 调用花费更多时间?
【发布时间】:2011-07-24 08:49:24
【问题描述】:

我正在线程上打个招呼,我创建了一个使用 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 multithreading


    【解决方案1】:

    从评论到接受回复:“您对此有何建议?”

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

    创建线程的成本很高。

    即使在线程运行你想要的任务之前,它也必须被创建,这是一个非常长的过程。正因如此,我们才有了线程池的概念。

    您不是每次要执行并发任务时都创建新线程,而是先创建线程,然后在需要时向它们发送任务。

    线程池是第二个优势。任务完成后,线程并没有被销毁,而是保持活动状态以运行下一个任务,因此线程创建成本只发生一次,在初始化时。

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

    首先创建一个带有线程池的执行器。为了简单起见,我们创建了一个有 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】:

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

      来自关于start 的 Thread 文档:

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

      【讨论】:

        【解决方案3】:

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

        创建 100,000 个线程在大多数当前计算机上性能不佳(我忽略了价值数百万美元的高端计算机)。一旦线程数量超过了 CPU 支持的容量,就会开始引发上下文切换。因此,如果您有一个四核系统,运行超过四个线程实际上会减慢您的程序速度(模 I/O 操作以及 CPU 无论如何都会空闲的情况)。

        【讨论】:

        • 我正在研究线程,因为我希望能够模拟目标方法调用上的负载,似乎不能简单地通过创建“100000”线程来完成..你有什么建议关于这个?
        • @Chin:显然不知道你实际上在做什么,但是:你可以使用Runtime.getRuntime().availableProcessors() 来找出你有多少可用的内核,并为每个运行一个紧密循环的线程启动一个线程重复调用该方法。
        • @Chin:simulate load on a targeted method call 是什么意思?它是一种剖析吗?线程有什么帮助?
        • @Leniik:我的意思是在我的 EJB 类中调用一个方法来模拟负载,即一次 100 个并发调用。 @见stackoverflow.com/questions/6805961/…
        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

          start 方法是神奇之处。它会产生一个新线程,这需要一些时间:

          • 创建线程
          • 启动线程
          • 管理线程生命周期

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

          【讨论】:

            【解决方案6】:

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

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

            可以在线程实例上调用 run() 方法,因为我们可以通过两种方式定义执行线程将执行的内容。

            • 旧的:扩展 Thread 的 run 方法。由于耦合等原因,不建议再使用它。

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

            注意,如果你创建了一个带有 Runnable(target) 的线程,并且还重写了线程的 run() 方法,你可以理解你当前提供了 2 个代码来执行,但是会执行哪一个呢?如果您查看 Thread 的默认 run() 实现,这很容易找到:

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

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

            正如我所说,有两种方法可以启动线程。我猜当他们设计“新方式”时,他们必须将 run() 方法保留在 Thread 类中以解决逆向兼容性问题,但如果他们可以及时返回,他们可能不会让 Thread 类实现 Runnable 接口,因此将减少线程必须如何启动的混乱......

            【讨论】:

              猜你喜欢
              • 2014-03-06
              • 1970-01-01
              • 2014-04-23
              • 2018-06-28
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-04-06
              • 1970-01-01
              相关资源
              最近更新 更多