为什么创建线程据说成本很高?

发布于 2024-10-28 03:44:45 字数 409 浏览 1 评论 0原文

Java 教程说创建线程的成本很高。但到底为什么它贵呢?创建 Java 线程时究竟发生了什么导致其创建成本高昂?我认为这个说法是正确的,但我只是对 JVM 中线程创建的机制感兴趣。

线程生命周期开销。线程的创建和销毁不是免费的。实际开销因平台而异,但线程创建需要时间,从而在请求处理中引入延迟,并且需要 JVM 和操作系统进行一些处理活动。如果请求频繁且轻量(就像在大多数服务器应用程序中一样),则为每个请求创建一个新线程可能会消耗大量计算资源。

来自实践中的 Java 并发
作者:Brian Goetz、Tim Peierls、Joshua Bloch、Joseph Bowbeer、David Holmes、Doug Lea
打印 ISBN-10:0-321-34960-1

The Java tutorials say that creating a Thread is expensive. But why exactly is it expensive? What exactly is happening when a Java Thread is created that makes its creation expensive? I'm taking the statement as true, but I'm just interested in mechanics of Thread creation in JVM.

Thread lifecycle overhead. Thread creation and teardown are not free. The actual overhead varies across platforms, but thread creation takes time, introducing latency into request processing, and requires some processing activity by the JVM and OS. If requests are frequent and lightweight, as in most server applications, creating a new thread for each request can consume significant computing resources.

From Java Concurrency in Practice
By Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, Doug Lea
Print ISBN-10: 0-321-34960-1

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

终陌 2024-11-04 03:44:45

为什么创建线程据说很昂贵

因为它>>是<<昂贵的。

Java 线程的创建成本很高,因为涉及大量工作:

  • 必须为线程堆栈分配和初始化大块内存。
  • 需要进行系统调用来创建/向主机操作系统注册本机线程。
  • 需要创建、初始化描述符并将其添加到 JVM 内部数据结构中。

从某种意义上说,它也是昂贵的,因为线程只要处于活动状态就会占用资源。例如,线程堆栈、可从堆栈访问的任何对象、JVM 线程描述符、操作系统本机线程描述符。

所有这些东西的成本都是特定于平台的,但在我遇到过的任何 Java 平台上它们都不便宜。


Google 搜索给我找到了一个旧基准据报道,在运行 2002 年老式 Linux 的 2002 年老式双处理器 Xeon 上的 Sun Java 1.4.1 上,线程创建速率约为每秒 4000 个。更现代的平台将提供更好的数字……我无法评论该方法……但至少它给出了线程创建可能花费的大概费用。

Peter Lawrey 的基准测试表明,目前线程创建的绝对速度显着加快,但尚不清楚其中有多少是由于 Java 和/或操作系统的改进……或更高的处理器速度。但他的数字仍然表明,如果使用线程池,与每次创建/启动新线程相比,性能会提高 150 倍以上。 (他指出这都是相对的......)


上面假设本机线程而不是绿色线程,但出于性能原因,现代 JVM 都使用本机线程。创建绿色线程可能更便宜,但您在其他方面需要付出代价。

更新:OpenJDK Loom 项目 旨在提供标准 Java 的轻量级替代方案线程等等。他们提出了虚拟线程,它是本机线程和绿色线程的混合体。简单来说,虚拟线程就像一个绿色线程实现,当需要并行执行时,它在底层使用本机线程。

截至目前(2023 年 7 月),Loom 项目已成为 JEP 444。它自 Java 19 以来一直处于预览状态,并建议在 Java 21 中完整发布。


我做了一些挖掘来了解 Java 线程的堆栈是如何真正分配的。对于 Linux 上的 OpenJDK 6,线程堆栈是通过调用创建本机线程的 pthread_create 来分配的。 (JVM 不会传递 pthread_create 预分配的堆栈。)

然后,在 pthread_create 内,通过调用 mmap 来分配堆栈,如下

mmap(0, attr.__stacksize, 
     PROT_READ|PROT_WRITE|PROT_EXEC, 
     MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)

所示 :对于 man mmapMAP_ANONYMOUS 标志会导致内存初始化为零。

因此,尽管将新的 Java 线程堆栈归零(根据 JVM 规范)可能并不重要,但实际上(至少对于 Linux 上的 OpenJDK 6)它们是归零的。

Why is creating a Thread said to be expensive?

Because it >>is<< expensive.

Java thread creation is expensive because there is a fair bit of work involved:

  • A large block of memory has to be allocated and initialized for the thread stack.
  • System calls need to be made to create / register the native thread with the host OS.
  • Descriptors need to be created, initialized and added to JVM-internal data structures.

It is also expensive in the sense that the thread ties down resources as long as it is alive; e.g. the thread stack, any objects reachable from the stack, the JVM thread descriptors, the OS native thread descriptors.

The costs of all of these things are platform specific, but they are not cheap on any Java platform I've ever come across.


A Google search found me an old benchmark that reports a thread creation rate of ~4000 per second on a Sun Java 1.4.1 on a 2002 vintage dual processor Xeon running 2002 vintage Linux. A more modern platform will give better numbers ... and I can't comment on the methodology ... but at least it gives a ballpark for how expensive thread creation is likely to be.

Peter Lawrey's benchmarking indicates that thread creation is significantly faster these days in absolute terms, but it is unclear how much of this is due improvements in Java and/or the OS ... or higher processor speeds. But his numbers still indicate a 150+ fold improvement if you use a thread pool versus creating/starting a new thread each time. (And he makes the point that this is all relative ...)


The above assumes native threads rather than green threads, but modern JVMs all use native threads for performance reasons. Green threads are possibly cheaper to create, but you pay for it in other areas.

Update: The OpenJDK Loom project aims to provide a light-weight alternative to standard Java threads, among other things. They are proposing virtual threads which are a hybrid of native threads and green threads. In simple terms, a virtual thread is rather like a green thread implementation that uses native threads underneath when parallel execution is required.

As of now (July 2023) Project Loom has become JEP 444. It has been in preview since Java 19, and is proposed for full release in Java 21.


I've done a bit of digging to see how a Java thread's stack really gets allocated. In the case of OpenJDK 6 on Linux, the thread stack is allocated by the call to pthread_create that creates the native thread. (The JVM does not pass pthread_create a preallocated stack.)

Then, within pthread_create the stack is allocated by a call to mmap as follows:

mmap(0, attr.__stacksize, 
     PROT_READ|PROT_WRITE|PROT_EXEC, 
     MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)

According to man mmap, the MAP_ANONYMOUS flag causes the memory to be initialized to zero.

Thus, even though it might not be essential that new Java thread stacks are zeroed (per the JVM spec), in practice (at least with OpenJDK 6 on Linux) they are zeroed.

雪落纷纷 2024-11-04 03:44:45

其他人讨论了线程的成本从何而来。这个答案涵盖了为什么创建线程与许多操作相比并不那么昂贵,但与任务执行替代方案相比相对昂贵,任务执行替代方案相对便宜。

在另一个线程中运行任务的最明显的替代方法是在同一线程中运行该任务。对于那些认为线程越多越好的人来说,这是很难理解的。逻辑是,如果将任务添加到另一个线程的开销大于您节省的时间,则在当前线程中执行任务可以更快。

另一种选择是使用线程池。线程池可以提高效率有两个原因。 1)它重用已经创建的线程。 2)您可以调整/控制线程数量以确保获得最佳性能。

以下程序打印......

Time for a task to complete in a new Thread 71.3 us
Time for a task to complete in a thread pool 0.39 us
Time for a task to complete in the same thread 0.08 us
Time for a task to complete in a new Thread 65.4 us
Time for a task to complete in a thread pool 0.37 us
Time for a task to complete in the same thread 0.08 us
Time for a task to complete in a new Thread 61.4 us
Time for a task to complete in a thread pool 0.38 us
Time for a task to complete in the same thread 0.08 us

这是对一个简单任务的测试,它暴露了每个线程选项的开销。 (此测试任务实际上是在当前线程中执行效果最好的任务。)

final BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
Runnable task = new Runnable() {
    @Override
    public void run() {
        queue.add(1);
    }
};

for (int t = 0; t < 3; t++) {
    {
        long start = System.nanoTime();
        int runs = 20000;
        for (int i = 0; i < runs; i++)
            new Thread(task).start();
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in a new Thread %.1f us%n", time / runs / 1000.0);
    }
    {
        int threads = Runtime.getRuntime().availableProcessors();
        ExecutorService es = Executors.newFixedThreadPool(threads);
        long start = System.nanoTime();
        int runs = 200000;
        for (int i = 0; i < runs; i++)
            es.execute(task);
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in a thread pool %.2f us%n", time / runs / 1000.0);
        es.shutdown();
    }
    {
        long start = System.nanoTime();
        int runs = 200000;
        for (int i = 0; i < runs; i++)
            task.run();
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in the same thread %.2f us%n", time / runs / 1000.0);
    }
}
}

如您所见,创建一个新线程仅花费约 70 µs。在许多(如果不是大多数)用例中,这可能被认为是微不足道的。相对而言,它比替代方案更昂贵,并且对于某些情况,线程池或根本不使用线程是更好的解决方案。

Others have discussed where the costs of threading come from. This answer covers why creating a thread is not that expensive compared to many operations, but relatively expensive compared to task execution alternatives, which are relatively less expensive.

The most obvious alternative to running a task in another thread is to run the task in the same thread. This is difficult to grasp for those assuming that more threads are always better. The logic is that if the overhead of adding the task to another thread is greater than the time you save, it can be faster to perform the task in the current thread.

Another alternative is to use a thread pool. A thread pool can be more efficient for two reasons. 1) it reuses threads already created. 2) you can tune/control the number of threads to ensure you have optimal performance.

The following program prints....

Time for a task to complete in a new Thread 71.3 us
Time for a task to complete in a thread pool 0.39 us
Time for a task to complete in the same thread 0.08 us
Time for a task to complete in a new Thread 65.4 us
Time for a task to complete in a thread pool 0.37 us
Time for a task to complete in the same thread 0.08 us
Time for a task to complete in a new Thread 61.4 us
Time for a task to complete in a thread pool 0.38 us
Time for a task to complete in the same thread 0.08 us

This is a test for a trivial task which exposes the overhead of each threading option. (This test task is the sort of task that is actually best performed in the current thread.)

final BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
Runnable task = new Runnable() {
    @Override
    public void run() {
        queue.add(1);
    }
};

for (int t = 0; t < 3; t++) {
    {
        long start = System.nanoTime();
        int runs = 20000;
        for (int i = 0; i < runs; i++)
            new Thread(task).start();
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in a new Thread %.1f us%n", time / runs / 1000.0);
    }
    {
        int threads = Runtime.getRuntime().availableProcessors();
        ExecutorService es = Executors.newFixedThreadPool(threads);
        long start = System.nanoTime();
        int runs = 200000;
        for (int i = 0; i < runs; i++)
            es.execute(task);
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in a thread pool %.2f us%n", time / runs / 1000.0);
        es.shutdown();
    }
    {
        long start = System.nanoTime();
        int runs = 200000;
        for (int i = 0; i < runs; i++)
            task.run();
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in the same thread %.2f us%n", time / runs / 1000.0);
    }
}
}

As you can see, creating a new thread only costs ~70 µs. This could be considered trivial in many, if not most, use cases. Relatively speaking it is more expensive than the alternatives and for some situations a thread pool or not using threads at all is a better solution.

眼泪都笑了 2024-11-04 03:44:45

理论上,这取决于 JVM。实际上,每个线程都有相对较大的堆栈内存(我认为默认为 256 KB)。此外,线程被实现为操作系统线程,因此创建它们涉及操作系统调用,即上下文切换。

请务必认识到,计算中的“昂贵”始终是相对的。线程创建相对于大多数对象的创建来说非常昂贵,但相对于随机硬盘查找来说并不是非常昂贵。您不必不惜一切代价避免创建线程,但每秒创建数百个线程并不是明智之举。在大多数情况下,如果您的设计需要大量线程,则应使用有限大小的线程池。

In theory, this depends on the JVM. In practice, every thread has a relatively large amount of stack memory (256 KB per default, I think). Additionally, threads are implemented as OS threads, so creating them involves an OS call, i.e. a context switch.

Do realize that "expensive" in computing is always very relative. Thread creation is very expensive relative to the creation of most objects, but not very expensive relative to a random harddisk seek. You don't have to avoid creating threads at all costs, but creating hundreds of them per second is not a smart move. In most cases, if your design calls for lots of threads, you should use a limited-size thread pool.

凉风有信 2024-11-04 03:44:45

有两种线程:

  1. 适当的线程:这些是围绕底层操作系统的线程设施的抽象。因此,线程的创建与系统的创建一样昂贵——总是存在开销。

  2. “绿色”线程:由 JVM 创建和调度,这些线程更便宜,但不会发生适当的并行性。它们的行为类似于线程,但在操作系统中的 JVM 线程内执行。据我所知,它们并不经常使用。

我能想到的线程创建开销中最大的因素是您为线程定义的堆栈大小。运行虚拟机时,线程堆栈大小可以作为参数传递。

除此之外,线程创建主要依赖于操作系统,甚至依赖于虚拟机实现。

现在,让我指出一点:如果您计划在运行时的每一秒每秒触发 2000 个线程,那么创建线程的成本就会很高。 JVM 并不是为处理这种情况而设计的。。如果你有几个稳定的工人不会一次又一次被解雇和杀害,那就放松点。

There are two kinds of threads:

  1. Proper threads: these are abstractions around the underlying operating system's threading facilities. Thread creation is, therefore, as expensive as the system's -- there's always an overhead.

  2. "Green" threads: created and scheduled by the JVM, these are cheaper, but no proper paralellism occurs. These behave like threads, but are executed within the JVM thread in the OS. They are not often used, to my knowledge.

The biggest factor I can think of in the thread-creation overhead, is the stack-size you have defined for your threads. Thread stack-size can be passed as a parameter when running the VM.

Other than that, thread creation is mostly OS-dependent, and even VM-implementation-dependent.

Now, let me point something out: creating threads is expensive if you're planning on firing 2000 threads per second, every second of your runtime. The JVM is not designed to handle that. If you'll have a couple of stable workers that won't be fired and killed over and over, relax.

甲如呢乙后呢 2024-11-04 03:44:45

创建线程需要分配相当数量的内存,因为它必须创建两个新堆栈(一个用于 Java 代码,一个用于本机代码),而不是一个。 执行器/线程池的使用可以通过为多个任务重用线程来避免开销 执行者

Creating Threads requires allocating a fair amount of memory since it has to make not one, but two new stacks (one for java code, one for native code). Use of Executors/Thread Pools can avoid the overhead, by reusing threads for multiple tasks for Executor.

紫﹏色ふ单纯 2024-11-04 03:44:45

显然,问题的关键在于“昂贵”意味着什么。

线程需要创建一个栈,并根据run方法初始化栈。

它需要设置控制状态结构,即它处于可运行、等待等状态。

围绕设置这些东西可能有大量的同步。

Obviously the crux of the question is what does 'expensive' mean.

A thread needs to create a stack and initialize the stack based on the run method.

It needs to set up control status structures, ie, what state it's in runnable, waiting etc.

There's probably a good deal of synchronization around setting these things up.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文