Skip to content

Latest commit

 

History

History
58 lines (28 loc) · 4.65 KB

08第八章:线程池的使用.md

File metadata and controls

58 lines (28 loc) · 4.65 KB

在 Executor 的实现中,如果从任务中抛出了一个未检查异常,那么将用一个新的工作线程来替代抛出异常的线程。

8.1.1 线程饥饿死锁

在单线程的 Executor 中,如果一个任务将另一个任务提交到同一个 Executor,并且等待这个被提交任务的结果,那么通常会发生死锁。第二个任务停留在工作队列中,并等待第一个任务完成,而第一个任务又无法完成,因为它在等待第二个任务。

在更大的线程池中,如果所有正在执行任务的线程都由于等待其他仍处于工作队列中的任务而阻塞,也会发生同样的问题。

只要线程池中的任务需要无限地等待一些必须由池中其他任务才能提供的资源或条件,例如某个任务等待另一个任务的返回值或执行结果,那么除非线程池足够大,否则将发生线程饥饿死锁。

8.2 设置线程池的大小

如果线程池过大,那么大量的线程将在相对很少的 CPU 和内存资源上发生竞争,这不仅会导致更高的内存使用量,而且还可能耗尽资源。

如果线程池过少,那么将导致许多空闲的处理器无法执行工作,从而降低吞吐率。

对于计算密集型的任务,在拥有 $N_{cpu}$ 个处理器的系统上,当线程池的大小为 $N_{cpu} + 1$ 时,通常能实现最优的利用率。

对于包含 I/O 操作或者其他阻塞操作的任务,由于线程并不会一直执行,因此线程池的规模应该更大。

8.3.1 线程的创建与销毁

线程池的基本大小 (Core Pool Size)、最大大小 (Maximum Pool Size)、以及存活时间等因素共同负责线程的创建与销毁。

线程池的基本大小即在没有执行任务时线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。

在创建 ThreadPoolExecutor 初期,线程并不会立即启动,而是等到有任务提交时才会启动,除非调用 prestartAllCoreThreads。

如果某个线程的空闲时间超过了存活时间,那么将被标记为可回收的,并且当线程池的当前大小超过了基本大小时,这个线程将被终止。

newFixedThreadPool 工厂方法将线程池的基本大小和最大大小设置为参数中指定的值,而且创建的线程池不会超时。

newCachedThreadPool 工厂方法将线程池的最大大小设置为 Integer.MAX_VALUE,而将基本大小设置为 0,并将超时设置为 1分钟。这种方法创建出来的线程池可以被无限扩展,并且当需求降低时会自动收缩。

8.3.2 管理队列任务

newFixedThreadPoolnewSingleThreadExecutor 在默认情况下将使用一个无界的 LinkedBlockingQueue。如果所有工作者线程都处于忙碌状态,那么任务将在队列中等候。如果任务持续快速地到达,并且超过了线程池处理它们的速度,那么队列将无限制地增加。

在使用有界的工作队列时,队列的大小与线程池的大小必须一起调节。如果线程池较小而队列较大,那么有助于减少内存使用量,降低 CPU 的使用率,同时还可以减少上下文切换,但付出的代价可能是会限制吞吐量。

LinkedBlockingQueueArrayBlockingQueue 这样的先进先出 (FIFO) 队列时,任务的执行顺序与它们的到达顺序相同。如果想进一步控制任务执行顺序,还可以使用 PriorityBlockingQueue,这个队列将根据优先级来安排任务。任务的优先级是通过自然顺序或 Comparator 来定义的。

8.3.3 饱和策略

当有界队列被填满后,饱和策略开始发挥作用。ThreadPoolExecutor 的饱和策略可以通过调用 setRejectedExecutionHandler 来修改。如果某个任务被提交到一个已被关闭的 Executor 时,也会用到饱和策略。

JDK 提供的饱和策略如下:

终止策略 (AbortPolicy):默认的饱和策略。该策略将抛出未检查的 RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。

抛弃策略 (DiscardPolicy):当新提交的任务无法保存到队列中等待执行时,该策略会抛弃该任务。

抛弃最旧策略 (DiscardOldestPolicy):该策略会抛弃下一个将被执行的任务,然后尝试重新提交新的任务。如果工作队列是一个优先队列,那么该策略将抛弃优先级最高的任务,因此最好不要将该策略和优先级队列放在一起使用。

调用者运行策略 (CallerRunsPolicy):该策略既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者,从而降低新任务的流量。