【Java 并发】Executor框架机制与线程池配置使用

时间:2022-03-05 15:42:32

【Java 并发】Executor框架机制与线程池配置使用

一,Executor框架Executor框架便是Java 5中引入的,其内部使用了线程池机制,在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,更易管理,效率更好(用线程池实现,节约开销)。

Executor框架主要包括:Executor,Executors,ExecutorService,AbstractExecutorService,ThreadPoolExecutor,CompletionService,Future,Callable等。

Executor

public interface Executor {
void execute(Runnable command);
}

Executor是一个简单的接口,但却是整个执行框架的基础,将任务的提交和执行分解开,并用Runnable 来表示任务,所以Executor也可看成基于生产者-消费者模式,提交任务的操作相当于生产者,执行任务的线程相当于消费者。Executor的实现还提供了对生命周期的支持,以及统计信息收集,应用程序管理机制和性能监视等机制。

ExecutorService

public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException;
//........其他方法

ExecutorService扩展Executor,是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,以及可跟踪一个或多个异步任务执行状况返回Future的方法。它主要包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入平缓关闭状态,ExecutorService不再接受新的任务,但它还在等待已经提交的任务完成,shutdownNow()就简单粗暴,直接尝试关闭所有任务,并不在接收任务。

AbstractExecutorServiceExecutorService执行方法的默认实现

ThreadPoolExecutor线程池,可以通过调用Executors以下静态工厂方法来创建线程池并返回一个ExecutorService对象。

Executors

提供了一系列静态工厂方法用于创建各种线程池:

1,newFixedThreadPool(int nThreads):创建一个线程池,该线程池重新使用一个固定数量的线程,该线程在共享的*队列中运行。在任何时候,大多数线程都将是活动的处理任务。如果在所有线程都处于活动状态时提交额外的任务,它们将在队列中等待,直到有一个线程可用。如果任何线程由于在关闭前的执行过程中出现故障而终止,那么在需要执行后续任务时,将会有一个新的线程被执行。池中的线程将存在,直到ExecutorService显式地执行服务关闭。

2,newCachedThreadPool():创建一个线程池,根据需要创建新的线程,但在可用时将重用以前构造的线程。这些池通常会提高执行许多短期异步任务的程序的性能。如果可用,对execute的调用将重用以前构造的线程。如果没有可用的现有线程,将创建一个新线程并将其添加到池中。在60秒内没有使用的线程被终止并从缓存中删除。因此,一个闲置时间足够长的池将不会消耗任何资源。具体超时时间可设置。

3,newSingleThreadExecutor() :创建一个执行器,该执行程序使用单个工作线程来操作一个*队列。(请注意,如果此单线程由于在关闭前执行失败而终止,则在需要执行后续任务时将会出现一个新的线程。)任务被保证按顺序执行,并且在任何给定的时间内都不会有一个任务是活动的。与其他等效的newFixedThreadPool(1)不同,返回的执行程序保证不会重新配置,以使用其他线程。

4, newScheduledThreadPool(int corePoolSize):创建一个线程池,该线程池可以调度命令在给定的延迟之后运行,或者定期执行。类似于Timer。

一般来说,CachedTheadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选,只有当这种方式会引发问题时(比如需要大量长时间面向连接的线程时),才需要考虑用FixedThreadPool。——《Thinking in Java》第四版

二,线程池配置使用

虽然Executor框架可以高效的管理线程,但是并非所有任务都适合Executor所有执行策略,这也会降低性能和效率。

1,线程饥饿死锁

只要线程池中的任务需要无限期的等待一些必须由池中其他任务才能提供的资源或者条件,但是该任务却还在工作队列中还未有线程去执行,这就会造成线程饥饿死锁。举个栗子,假设只有单线程Executor,此时A任务正在运行,A任务等待B任务的结果才可继续,于是就等待,但是此时是单线程,B任务一直无法运行,它在等A任务结束。于是两个任务互相等待,造成死锁。在更大的线程池中也能出现,除非线程池足够大。

提交这种有依赖性的Executor,就需要避免饥饿死锁,因此需要考虑线程池大小限制或者配置限制。

2,运行长时间任务

执行长时间的任务不仅会造成线程池阻塞,甚至还会增加执行时间短任务的服务时间,降低了Executor管理的服务的响应性。

缓解长时间任务造成的影响,可以限定任务等待资源的时间。如果等待超时,那么标记任务失败,然后中止任务或者稍后再执行任务。这种办法保证任务总能继续执行下去。如果线程池总是阻塞,那么就要考虑线程池是否设置过小。

ThreadPoolExecutor提供了构造方法,可以根据需求配置线程池,进行定制

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) //后两个参数为可选参数

具体的ThreadPoolExecutor参数配置

corePoolSize

核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。

maximumPoolSize

当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。

keepAliveTime:线程池维护线程所允许的空闲时间

unit:线程池维护线程所允许的空闲时间的单位

workQueue:线程池所使用的缓冲队列

handler:线程池对拒绝任务的处理策略

当一个任务通过execute(Runnable)方法欲添加到线程池时:

1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。

2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行 。

3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务。

4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理 。

5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程

6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭。

java学习群669823128