【问题标题】:Are there performance implications to creating a Thread and never starting it?创建线程并且从不启动它是否会对性能产生影响?
【发布时间】:2018-11-14 08:16:40
【问题描述】:

我正在处理现有的 Java 代码库,它有一个扩展 Thread 的对象,还包含许多与线程本身的操作有关的其他属性和方法。在代码库的早期版本中,线程始终是实际的重量级 Thread,以 Thread.start 开头,以 Thread.join 等待,等等。

我目前正在重构代码库,在当前版本中,并不总是需要对象的 Thread 功能(但由于对象中包含其他功能,对象本身是可用的;在许多情况下,它是可用的即使线程本身没有运行)。因此在某些情况下,应用程序会创建这些对象(扩展 Thread)并且从不对它们调用 .start(),纯粹将它们用于其他属性和方法。

将来,应用程序可能需要创建比以前更多的这些对象,以至于我可能需要担心性能问题。显然,创建和启动大量实际线程将是一场性能噩梦。同样的事情是否适用于从未启动的Thread 对象?也就是说,创建Thread 是否需要任何操作系统资源或大型Java 资源?还是仅当Thread 实际上是.started 时才使用资源,从而使未启动的Thread 对象可以安全地大量使用?可以重构代码以将非线程相关的功能拆分为单独的函数,但如果这样做完全没有意义,我不想进行大规模的重构。

我已尝试通过一些网络搜索来确定此问题的答案,但很难确定查询的目标,因为搜索引擎通常无法将 Thread 对象与实际的 Java 线程区分开来。

【问题讨论】:

  • 我不知道仅仅实例化对象是否已经为线程的堆栈分配了内存,但是如果它不是线程,为什么还要继承线程呢?
  • “但对象本身是可用的,即使线程本身没有运行”——这在我看来是一个糟糕的设计。我认为您应该将一些功能分开并将其放在其他地方。
  • 唯一的副作用是炸毁ThreadLocals 中的条目
  • @MauricePerry:它做了一些可以同步或异步发生的簿记。如果它是异步执行的,则线程接收命令以通过 InputStream 运行;如果它是同步完成的,则可以通过直接对对象的方法调用来完成,因此不需要线程。回到创建对象时,它总是被异步使用,因此使对象成为线程是有意义的;重构代码以支持同步案例使它变得不那么有意义,我正在尝试计算需要花费多少精力来进一步重构它。
  • 除非对象有很多字段,否则我认为您可以毫无问题地拥有数百万个字段。

标签: java multithreading performance


【解决方案1】:

您可以实现Runnable 而不是扩展Thread

public class MyRunnableClass implements Runnable {

    // Your stuff...

    @Override
    public void run() {
        // Thread-related stuff...
    }

}

当您需要运行Object 以充当Thread 时,只需使用:

Thread t = new Thread(new MyRunnableClass());
t.start();

【讨论】:

  • 或者使用 lambda 表达式,如果您不直接需要对象内的属性:Runnable r = () -> doSomeWork();。调用new Thread(r).start()触发其运行。
【解决方案2】:

正如其他人指出的那样:性能在这里不是问题。

我会更多地关注“好的设计”方法。当您不打算调用 start() 时,扩展 Thread 根本没有(很多,任何?)意义。你会看到:你编写代码来传达你的意图。

扩展线程而不将其用作线程,这只会传达混乱。您的代码的每个新的未来读者都会想知道“为什么会这样”?

因此,请专注于实现直截了当的设计。我会更进一步:不要只是转向Runnable,而是继续使用线程。相反:了解 ExecutorServices,以及如何提交任务、Futures 等等。

“Bare Iron”线程(和 Runnables)就像 20 年前的概念。 Java 现在有更好的东西可以提供。所以,如果你真的很想改进你的代码库:看看这些新的抽象概念,找出它们在哪里使用是有意义的。

【讨论】:

    【解决方案3】:

    每 GB 内存可以创建大约 150 万个此类对象。

    import java.util.LinkedList;
    import java.util.List;
    
    class A {
        public static void main(String[] args) {
            int count = 0;
            try {
                List<Thread> threads = new LinkedList<>();
                while (true) {
                    threads.add(new Thread());
                    if (++count % 10000 == 0)
                        System.out.println(count);
                }
            } catch (Error e) {
                System.out.println("Got " + e + " after " + count + " threads");
            }
        }
    }
    

    在 Oracle Java 8 中使用 -Xms1g -Xmx1g,进程在大约停止

    1 GB - 1780000
    2 GB - 3560000
    6 GB - 10690000
    

    该对象的使用量比您阅读源代码时所预期的要多,但每个仍然大约 600 字节。

    注意:Throwable 使用的内存也比您通过阅读 Java 源代码所预期的要多。它可以多出 500 - 2000 字节,具体取决于创建时堆栈的大小。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多