Threadpool 如何重用线程以及它是如何工作的

Posted

技术标签:

【中文标题】Threadpool 如何重用线程以及它是如何工作的【英文标题】:How Threadpool re-use Threads and how it works 【发布时间】:2013-11-14 23:08:38 【问题描述】:

我的多线程概念很薄弱,正在努力学习。

据我所知,在 Java 中,我们不能多次调用线程:

Thread t = new Thread; //Some Runnable
t.start();

t.start(); //Illegal and throw Exception at runtime.

据我所知,当您再次调用 t.start() 时,它会引发异常,因为线程的关联堆栈一旦退出 run() 方法并且您尝试再次初始化,就会被销毁。

在这种情况下,我对线程池的了解是,它可以提供更好的性能并节省时间,因为不需要创建新线程(我在this 中读到)。

如果在线程池场景中不需要创建新线程,那么它如何与刚刚完成其运行方法的同一线程一起工作,该线程是否可以再次使用?

我读过this,它说“java.util.concurrent 中的大多数执行器实现都使用线程池,它由工作线程组成。这种线程与 Runnable 和 Callable 任务分开存在它执行并经常用于执行多个任务。"

那么这里的 Worker 线程是什么,它和普通的 Java 线程有什么不同吗?

通过this 链接,我得到了一些东西,但仍然对使用线程池可以消除哪些东西以及为什么它比使用普通 java 线程提供更好的性能感到困惑。

所以我们可以这样说,

线程分为三个部分,

    创建(告诉操作系统它是新线程,为其创建堆栈。) 使用 run() 方法执行 Runnable。 销毁线程。

因此,考虑到以上 3 个步骤,线程池的第 1 步和第 3 步可以在创建固定数量的线程后消除。每个任务只会执行第 2 步,这就是为什么线程池更快?我们可以这样说吗?我说的对吗?

【问题讨论】:

这是simple, open-source Java thread pool。您实际上可以检查代码以了解它是如何工作的,在后台 您指向的链接在 ThreadPool 构造函数中创建了 n 个 WorkerThread 对象并在其上调用 start() 方法,这只不过是创建 n 个 Thread 对象并在其上调用 start 方法,然后 Threadpool 如何帮助提高性能。 this link 可能会有所帮助 @Jayesh 空闲线程不会打扰系统,坏事是创建和销毁线程太多,实际上线程池多次使用线程而不是销毁并再次创建它。 this link 也可以描述这个问题。 @Jayesh 每个线程都需要它自己的堆栈,操作系统也应该切换(switch-context)并管理线程。除了线程对某些工作非常有效之外,它们也会成为敌人,拥有太多线程并不是很好(至少对于 CPU 而言)。使用 trhead 池,您完成了两件(一般)事情。 1.多次使用(回收)线程 2.通过限制创建多个线程来管理内存。您提到的时间是用于切换上下文和线程私有内存,每次创建和销毁都需要时间。 【参考方案1】:

如果在 ThreadPool 场景中不需要创建新的线程,那么它如何与刚刚完成其运行方法的同一线程一起工作,是否可以再次使用该线程?

简单 - 原始线程从未真正完成。它只是等待另一个任务执行。在伪代码中:

// No, this isn't even slightly accurate! General impression only :)
while (!pool.isShutdown()) 
    Runnable task = pool.waitForTaskOnQueue();
    task.run();

(很明显,当线程池关闭时,它也需要停止等待线程等待另一个任务 - 但希望您能大致了解。)

【讨论】:

已经够近了。可悲的是,太多的线程教程似乎没完没了地关于创建线程、让它们运行并等待它们用 join() 终止。如果幸运的话,它可能会在第 17 页的某处提到,您可以在其中放置一个 while 循环并在顶部等待信号。如果 Thread.join() 被弃用,我不会不高兴:) 但是要达到多线程的效果,需要创建几个线程?当你调用 executor.execute(runnable1), executor.execute(runnable2)..... 但是“task.run();”不需要在新线程中启动,那么它就可以作为多线程工作? @Jayesh:如果你需要并行执行,你需要一个线程池来拥有多个线程。但是每个线程在其生命周期中仍可能执行多个任务。将“支持处理多个任务”与“支持 N 路并行性”分开是很重要的。每个线程一次只能运行一个任务,是的。 @JonSkeet:您能否解释一下“支持 N 路并行性”中的“支持处理多个任务”。我明白了,但还不清楚。你能编辑你的答案并详细解释这一点吗?或者你能指出一些解释这些事情的链接。谢谢。 @Jayesh:假设您有一个包含 5 个线程的线程池。您可以向该线程池提交 1000 个任务,它们都会被执行 - 但一次只能执行 5 个。它们不会分批成 5 个块 - 每个线程在完成前一个任务时只会接手另一个任务。对于短期任务,这通常比创建 1000 个线程更有效率……毕竟,您不太可能首先拥有 1000 个 CPU 内核,并且线程创建/调度是一种开销。【参考方案2】:

该过程分为两部分:

提交任务:线程池与阻塞队列紧密耦合。当我们说 executor.execute(runnable)。 runnable/callable 在队列中排队。

任务的执行:现在需要从队列中提取任务。假设每当一个任务在队列中提交时,它必须被拾取并执行。

所以有些线程将运行无限循环并监视队列中的任务。一旦任务可用,一个线程就会选择它并执行。

【讨论】:

【参考方案3】:

在线程池中 线程池不是在新任务到达时创建新线程,而是保留许多空闲线程,这些线程准备好根据需要执行任务。线程完成任务执行后,它不会死亡。相反,它在池中保持空闲状态,等待被选择执行新任务。

您可以限制池中一定数量的并发线程,这对于防止过载很有用。如果所有线程都忙于执行任务,则将新任务放入队列中,等待线程变为可用

【讨论】:

【参考方案4】:

所以,考虑到以上 3 个步骤,使用 Threadpool step 1 和 Step 3 可以 在固定数量的线程创建后被淘汰。只有第 2 步 每个任务都会被执行,这就是为什么 Threadpool 更快?我们可以吗 这么说?我说的对吗?

是的,你是对的。线程创建和销毁是一项代价高昂的任务。由于在线程池中已经创建了线程,因此不存在线程创建的开销。但是,如果您的线程数比应有的多得多,那么这对您的应用程序将非常不利。它可能会去OutofMemorry 或者可能会遇到一些其他问题。因此,要修复线程池大小,请使用以下公式:

no of threads = 2 * no_of_cores * no_of_disks * percentage CPU utilization you need * (1 + (W/ C))

(W/C) 是表示等待时间与计算时间的分数。

【讨论】:

***.com/questions/4759570/… 在 Java 中查找内核数 :)

以上是关于Threadpool 如何重用线程以及它是如何工作的的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot Async 方法如何使用 ThreadPool 处理请求

多线程之旅(ThreadPool 线程池)

线程池(ThreadPool)

Android 开发 ThreadPool(线程池) 总结

如何在工作线程中重用主线程创建的OMP线程池?

如何中止使用 ThreadPool.QueueUserWorkItem 创建的线程