Java 线程池

2025 年 3 月 28 日 | 阅读 8 分钟

Java 线程池代表了一组等待任务并可重复使用的工作线程。

在线程池的情况下,会创建一个固定大小的线程组。服务提供商会从线程池中拉出一个线程并为其分配任务。任务完成后,线程会再次放入线程池中。

Executer 线程池方法

newFixedThreadPool(int s): 此方法创建一个固定大小为 s 的线程池。

newCachedThreadPool(): 此方法创建一个新的线程池,该线程池在需要时创建新线程,但仍会尽可能使用先前创建的可用线程。

newSingleThreadExecutor(): 此方法创建一个新线程。

Java 线程池的优点

更好的性能 它节省了时间,因为无需创建新线程。

实际应用

它用于 Servlet 和 JSP,其中容器创建线程池来处理请求。

Java 线程池示例

让我们通过 ExecutorService 和 Executors 看一个简单的 Java 线程池示例。

文件:WorkerThread.java

文件:TestThreadPool.java

输出

pool-1-thread-1 (Start) message = 0
pool-1-thread-2 (Start) message = 1
pool-1-thread-3 (Start) message = 2
pool-1-thread-5 (Start) message = 4
pool-1-thread-4 (Start) message = 3
pool-1-thread-2 (End)
pool-1-thread-2 (Start) message = 5
pool-1-thread-1 (End)
pool-1-thread-1 (Start) message = 6
pool-1-thread-3 (End)
pool-1-thread-3 (Start) message = 7
pool-1-thread-4 (End)
pool-1-thread-4 (Start) message = 8
pool-1-thread-5 (End)
pool-1-thread-5 (Start) message = 9
pool-1-thread-2 (End)
pool-1-thread-1 (End)
pool-1-thread-4 (End)
pool-1-thread-3 (End)
pool-1-thread-5 (End)
Finished all threads

线程池示例:2

让我们看另一个线程池的例子。

文件名: ThreadPoolExample.java

输出

Initialization time for the task name: task 1 = 06 : 13 : 02
Initialization time for the task name: task 2 = 06 : 13 : 02
Initialization time for the task name: task 3 = 06 : 13 : 02
Time of execution for the task name: task 1 = 06 : 13 : 04
Time of execution for the task name: task 2 = 06 : 13 : 04
Time of execution for the task name: task 3 = 06 : 13 : 04
Time of execution for the task name: task 1 = 06 : 13 : 05
Time of execution for the task name: task 2 = 06 : 13 : 05
Time of execution for the task name: task 3 = 06 : 13 : 05
Time of execution for the task name: task 1 = 06 : 13 : 06
Time of execution for the task name: task 2 = 06 : 13 : 06
Time of execution for the task name: task 3 = 06 : 13 : 06
Time of execution for the task name: task 1 = 06 : 13 : 07
Time of execution for the task name: task 2 = 06 : 13 : 07
Time of execution for the task name: task 3 = 06 : 13 : 07
Time of execution for the task name: task 1 = 06 : 13 : 08
Time of execution for the task name: task 2 = 06 : 13 : 08
Time of execution for the task name: task 3 = 06 : 13 : 08
task 2 is complete.
Initialization time for the task name: task 4 = 06 : 13 : 09
task 1 is complete.
Initialization time for the task name: task 5 = 06 : 13 : 09
task 3 is complete.
Time of execution for the task name: task 4 = 06 : 13 : 10
Time of execution for the task name: task 5 = 06 : 13 : 10
Time of execution for the task name: task 4 = 06 : 13 : 11
Time of execution for the task name: task 5 = 06 : 13 : 11
Time of execution for the task name: task 4 = 06 : 13 : 12
Time of execution for the task name: task 5 = 06 : 13 : 12
Time of execution for the task name: task 4 = 06 : 13 : 13
Time of execution for the task name: task 5 = 06 : 13 : 13
Time of execution for the task name: task 4 = 06 : 13 : 14
Time of execution for the task name: task 5 = 06 : 13 : 14
task 4 is complete.
task 5 is complete.

解释: 从程序的输出可以看出,任务 4 和 5 仅在线程空闲时执行。在那之前,额外的任务会被放入队列。

以上示例的启示是,当一个人想执行 50 个任务但不愿创建 50 个线程时。在这种情况下,可以创建一个包含 10 个线程的池。因此,分配了 10 个任务中的 10 个,其余的放入队列。每当 10 个线程中的任何一个空闲时,它就会拾取第 11 个任务。其他待处理任务的处理方式相同。

线程池中涉及的风险

线程池中涉及的风险如下:

死锁: 众所周知,死锁会出现在任何涉及多线程的程序中,而线程池会引入另一种死锁场景。考虑一种情况,所有正在执行的线程都在等待被阻塞并因线程不可用而等待在队列中的线程的结果。执行。

线程泄漏: 当一个线程被从池中移除以执行任务,但在任务完成后没有返回到池中时,就会发生线程泄漏。例如,当线程抛出异常并且池类无法捕获此异常时,线程将退出,并且线程池的大小将减少 1。如果相同的情况重复多次,那么池很可能会变空,因此,池中没有线程可用于执行其他请求。

资源颠簸: 当线程池的大小非常大时,在线程之间进行上下文切换会浪费大量时间。当线程数超过最佳数量时,可能会导致饿死问题,并导致资源颠簸。

注意事项

不要将正在并发等待其他任务结果的任务排队。这可能导致死锁情况,如上所述。

使用长时间运行的操作的线程时必须小心。这可能导致线程永远等待,最终导致资源泄漏。

最后,必须显式终止线程池。如果这样做,程序将继续执行,永远不会结束。调用执行器上的 shutdown() 方法来终止执行器。请注意,如果有人在 shutdown 后尝试向执行器发送另一个任务,它将抛出 RejectedExecutionException。

为了有效地调整线程池,需要了解任务。如果给定的任务是对比的,那么应该寻找执行不同类型任务的池,以便可以适当地调整它们。

为了减少 JVM 内存不足的可能性,可以控制 JVM 中可以运行的最大线程数。线程池达到最大限制后将无法创建新线程。

如果线程已完成执行,线程池可以使用同一个已使用的线程。因此,节省了创建新线程的时间和资源。

调整线程池

线程池的准确大小由可用处理器数量和线程需要执行的任务类型决定。如果一个系统有 P 个处理器,并且只有计算类型的进程,那么 P 或 P + 1 的线程池的最大大小可以达到最大效率。但是,任务可能需要等待 I/O,在这种情况下,需要考虑请求的等待时间 (W) 和服务时间 (S) 的比率;因此,最大池大小为 P * (1 + W / S) 可实现最大效率。

结论

线程池是组织应用程序(尤其是在服务器端)的一个非常方便的工具。从概念上讲,线程池很容易理解。但是,在处理线程池时,可能需要考虑很多问题。这是因为线程池本身存在一些风险(风险如上所述)。


下一主题Java 线程组