Java教程说创建线程的代价很高。但它到底为什么昂贵呢?当创建Java线程时,究竟发生了什么,导致其创建成本高昂?我认为这句话是正确的,但我只对JVM中的线程创建机制感兴趣。
线程生命周期开销。线程创建和拆卸不是免费的。实际开销因平台而异,但是创建线程需要时间,这会在请求处理中引入延迟,并且需要JVM和OS进行一些处理活动。如果请求是频繁和轻量级的,就像在大多数服务器应用程序中一样,为每个请求创建一个新线程可能会消耗大量计算资源。
从实践中的Java并发性
作者: Brian Goetz,Tim Peierls,Joshua Bloch,Joseph Bowbeer,David Holmes,Doug Lea
打印ISBN-10: 0-321-34960-1
发布于 2011-03-30 15:16:01
为什么创建线程的代价很高?
因为它>>is<<昂贵。
Java线程的创建代价很高,因为涉及到大量的工作:
线程只要还活着就会占用资源,这也是代价高昂的;例如,线程堆栈、可从堆栈到达的任何对象、JVM线程描述符、OS本机线程描述符。
所有这些东西的成本都是特定于平台的,但它们在我遇到的任何Java平台上都不便宜。
谷歌搜索我发现了一个old benchmark,它报告在运行2002年的Linux的双处理器Xeon上的Sun Java 1.4.1上的线程创建速度为每秒大约4000次。一个更现代的平台会给出更好的数字。我不能评论这个方法..。但至少它为创建线程的代价提供了一个大概的估计。
Peter Lawrey的基准测试表明,现在线程创建的绝对速度要快得多,但尚不清楚这在多大程度上是由于Java和/或操作系统的改进……或者更高的处理器速度。但他的数字仍然表明,与每次创建/启动一个新线程相比,如果使用线程池,150+的性能会提高一倍。(他指出这一切都是相对的……)
上面假设使用本机线程,而不是绿色线程,但是出于性能原因,现代JVM都使用本机线程。创建绿色线程的成本可能更低,但您可以在其他领域为其买单。
更新: OpenJDK Loom project的目标是为标准Java线程提供一个轻量级的替代方案。他们提出了虚拟线程,它是本机线程和绿色线程的混合体。简单地说,当需要并行执行时,虚拟线程更像是一个绿色线程实现,它使用底层的本机线程。
截至目前(2021年1月),Project Loom的工作仍处于原型阶段,(AFAIK)没有针对发布的Java版本。
我做了一些深入研究,以了解Java线程的堆栈是如何真正分配的。在Linux6的情况下,线程堆栈是通过调用创建本机线程的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 mmap
的说法,MAP_ANONYMOUS
标志会导致内存初始化为零。
因此,即使新的Java线程栈可能不是必须的(根据JVM规范),但在实践中(至少对于Linux上的OpenJDK 6),它们是零的。
发布于 2011-03-30 15:53:03
其他人已经讨论了线程化的成本来自哪里。这个答案涵盖了为什么创建线程与许多操作相比没有那么昂贵,但与任务执行替代方案相比相对昂贵,后者相对便宜。
在另一个线程中运行任务的最明显的替代方案是在同一线程中运行该任务。对于那些认为线程越多越好的人来说,这是很难理解的。逻辑是,如果将任务添加到另一个线程的开销大于您节省的时间,则在当前线程中执行该任务可能会更快。
另一种选择是使用线程池。线程池可以更高效,原因有两个。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微秒,这在许多用例中是微不足道的,如果不是大多数的话。相对而言,它比替代方案更昂贵,对于某些情况,线程池或根本不使用线程是更好的解决方案。
发布于 2011-03-30 15:16:14
理论上,这取决于JVM。在实践中,每个线程都有相对较大的堆栈内存(默认情况下,我认为是256KB)。此外,线程被实现为OS线程,因此创建它们涉及OS调用,即上下文切换。
一定要认识到,计算中的“昂贵”总是非常相对的。与大多数对象的创建相比,线程创建的开销非常大,但相对于随机的硬盘寻道,线程创建的开销并不大。您不必不惜一切代价避免创建线程,但每秒创建数百个线程并不是明智之举。在大多数情况下,如果您的设计需要大量线程,则应使用大小有限的线程池。
https://stackoverflow.com/questions/5483047
复制相似问题