Executors 虽然提供了创建5中不同类型的线程池,但由于线程过大、队列过大等各种原因,在业内被禁用。所以本章不会将源码一一分析,只会大体介绍下主要方法。
Executors是Java标准库中的一个工具类,用于管理和创建线程池。可以更加方便地管理线程的创建、调度和回收,达到简化多线程编程的目的。Executors提供了一下作用:
管理线程池:Executors 提供了一系列的静态方法来创建不同种类的线程池,包括固定大小线程池、缓存线程池、单线程池、可调度线程池等。这些线程池可以用来处理不同类型的任务,同时也提供了一些配置选项,例如线程池大小、线程池拒绝策略等,可以更好地适应各种不同的场景。
异步执行任务:Executors 提供了一种方便的方式来异步执行任务。可以将任务包装成 Runnable 或 Callable 对象,并提交给线程池执行。线程池会自动管理线程的创建和回收,从而使任务的执行更加高效和可控。
调度执行任务:Executors 还提供了一些调度任务的方法,例如使用定时器来执行周期性任务或者延迟执行任务。这些调度方法可以非常方便地处理需要按照时间顺序执行的任务。
并发编程工具类:Executors 还提供了一些并发编程工具类,例如 CountDownLatch、CyclicBarrier 等,可以方便地处理一些常见的并发编程问题。
Executors 是 Java 提供的一个工具类,可以方便地创建不同类型的线程池。使用 Executors 创建线程池的步骤如下:
调用 Executors 的静态工厂方法创建需要类型的线程池。
// 创建固定大小的线程池,最多同时运行 nThreads 个线程
ExecutorService executor = Executors.newFixedThreadPool(nThreads);
// 创建只有一个线程的线程池,所有任务依次被执行
ExecutorService executor = Executors.newSingleThreadExecutor();
// 创建可缓存的线程池,线程数根据需求自动调整
ExecutorService executor = Executors.newCachedThreadPool();
// 创建支持延迟执行和定时执行的线程池
ScheduledExecutorService executor = Executors.newScheduledThreadPool(corePoolSize);
提交任务到线程池中执行
// 提交任务到线程池中执行。
executor.submit(task);
关闭线程池
// 关闭线程池。
executor.shutdown();
案例:创建固定大小的线程池
使用 Executors 创建一个固定大小的线程池,最多同时运行 2个线程,并提交 10 个任务到线程池中执行。
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
// 创建固定大小的线程池,最多同时运行2个线程
ExecutorService executor = Executors.newFixedThreadPool(2);
// 提交 10 个任务到线程池中执行
for (int i = 0; i < 10; i++) {
final int taskNum = i;
executor.submit(() -> {
System.out.println("Task " + taskNum + " is running.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskNum + " is completed.");
});
}
// 关闭线程池
executor.shutdown();
}
}
执行结果:
可以看到,线程池同时运行了2个任务,其余任务通过任务队列等待执行。每个任务执行完后,线程池中的线程并没有销毁,而是等待执行下一个任务。直到所有任务都执行完成后,线程池再被关闭。
阿里巴巴开发手册中明确禁用使用Executors来创建线程池。原因是因为Executors 类提供的线程池工厂方法创建的线程池有一些缺陷,容易导致系统性能下降和资源浪费。具体原因如下:
队列过大:Executors 创建的线程池中,允许的创建线程任务队列容量为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。(FixedThreadPool和SingleThreadPool)
线程过大:Executors 创建的线程池中,允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。(CachedThreadPool和newScheduledThreadPool)
拒绝策略不合理:当线程池和任务队列都已满,无法再接受新的任务时,Executors 采用的默认拒绝策略是 AbortPolicy,即直接抛出异常。这可能导致任务丢失,而且不能很好地反映系统的状态。
不支持线程池的优雅关闭:线程池的优雅关闭是保证应用程序正常退出的重要手段,可以让线程池中的任务有机会完成并保存状态。但是 Executors 并不提供一种良好的线程池关闭方式,如果强制关闭线程池,就可能导致线程池中的任务被强制中断,无法完成。
异常处理不够灵活:通过 Executors 提交的任务如果抛出异常,线程池默认会停止任务执行,并抛出异常。这种处理方式可能会影响其他任务的执行。如果需要更加灵活的异常处理方式,需要手动实现线程池的异常处理逻辑。
因此,阿里巴巴建议开发人员使用手动创建 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 来创建线程池,并根据具体的场景和需求,选择合适的线程池类型和参数,以避免以上缺点带来的问题。同时,阿里巴巴也提供了自己的线程池工具类,如 HystrixThreadPool 和 DisruptorThreadPool,以满足不同的业务需求。
Executors中常见有以下五种队列,
LinkedBlockingQueue:基于链表实现的无界阻塞队列,可以无限制添加元素,当队列为空时,消费者线程必须等待,直到队列中有元素被添加。
ArrayBlockingQueue:基于数组实现的有界阻塞队列,固定大小,当队列满时,生产者线程必须等待,直到队列中的元素被消费后才能继续添加元素。
SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,反之亦然。因此,该队列通常用于线程间的直接传递。
PriorityBlockingQueue:基于优先级实现的无界阻塞队列,元素按照优先级顺序被移除,如果多个元素具有相同的优先级,则按照它们在队列中的顺序被移除。
DelayQueue:基于时间实现的延迟阻塞队列,队列中的元素必须实现 Delayed 接口,元素只有在延迟期满时才能被取出。
不同队列的选择不仅仅影响任务的调度和处理,还会影响线程池的性能和稳定性。因此,在使用 Executors 创建线程池时,需要仔细考虑队列的特性和线程池的工作负载,以确保系统的高效和稳定。
Executors 提供了一系列静态工厂方法,用于创建不同类型的线程池,如:
newFixedThreadPool(int nThreads):创建一个固定大小的线程池,该线程池中的线程数量始终为 nThreads。
newWorkStealingPool() : 创建一个工作窃取线程池,该线程池会根据需要自动增加或减少线程数量,以最大限度地利用 CPU 资源。
newCachedThreadPool():创建一个可缓存的线程池,该线程池中的线程数量根据需要自动调整,线程空闲一定时间后将被回收。
newSingleThreadExecutor():创建一个单线程的线程池,该线程池中只有一个线程在工作,保证所有任务按照顺序执行。
newScheduledThreadPool(int corePoolSize):创建一个固定大小的线程池,该线程池可以进行定时任务和周期性任务的调度。
Executors 类中的newFixedThreadPool() 方法,用于创建一个固定大小的线程池,该线程池中的线程数量始终保持不变。一旦创建了线程池,就会立即创建固定数量的线程,并将这些线程放入线程池中,以便执行提交的任务。具体实现是通过 ThreadPoolExecutor 类来实现的。
/**
* corePoolSize: 核心线程数 nThreads
* maximumPoolSize:线程池中允许的最大线程数,nThreads
* keepAliveTime:线程池中空闲线程的存活时间,由于线程数是固定的,所以这个值是没有意义的,则是 0
* unit:存活时间的时间单位。
* workQueue:任务队列,这里使用的是 LinkedBlockingQueue,它是一个基于链表的阻塞队列,如果任务提交到线程池时,线
* 程数已经达到了最大值,则会将任务放入队列中等待执行。没有空闲线程可用,就会直接创建一个新线程来执行务。
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads,
nThreads,
0L,
TimeUnit.MILLISECONDS, // 毫秒
new LinkedBlockingQueue());
}
newFixedThreadPool 适用于那些需要控制线程数量并且提交的任务数量相对稳定的场景,可以有效地避免线程数量过多导致的资源消耗和系统性能下降的问题。
工作窃取线程池是一种基于任务分配的线程池,它使用一种叫做工作窃取的算法来提高线程利用率。每个线程都有一个任务队列,当线程完成自己的任务后,会从其他线程的队列中窃取任务来执行。这种机制可以避免线程因为等待其他线程而闲置的情况,从而提高系统的并发性能。
工作窃取线程池特点:
1.线程数量固定:工作窃取线程池中的线程数量是固定的,通常与处理器核心数量相关联。每个线程都维护着一个任务队列,用于存储需要处理的任务。
2.任务窃取:工作窃取线程池中的线程可以自动地对其他线程的任务进行窃取。当一个线程的任务队列为空时,该线程会从其他线程的任务队列中窃取任务进行处理。这种任务窃取机制可以实现负载均衡和高效的任务处理。
Executors 类中的newWorkStealingPool() 方法,用于创建一个工作窃取线程池,该线程池中的线程数量固定,但线程之间可以自动地对任务进行工作窃取,从而实现负载均衡和高效的任务处理。具体实现是通过 ForkJoinPool 类来实现的。
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(), // 工作线程数。
ForkJoinPool.defaultForkJoinWorkerThreadFactory, // 工作线程工厂
null, // 任务拒绝策略,即当任务无法被提交时,会抛出异常。
true); // 是否异步执行任务,这里设置为 true,即使用异步模式执行任务。
}
newWorkStealingPool 适用于那些需要处理大量任务,并且任务之间相互独立的场景,可以实现高效的任务并发处理,并通过任务窃取机制实现负载均衡和高效的任务处理。
Executors 类中的newCachedThreadPool() 方法,用于创建一个可缓存的线程池,该线程池中的线程数量可以根据需要进行自动调整。当有新的任务提交到线程池中时,如果当前线程池中有空闲线程可用,那么任务就会被分配给这些空闲线程执行。如果当前线程池中没有空闲线程可用,那么就会创建新的线程,并将任务分配给这些新线程执行。具体实现是通过 ThreadPoolExecutor 类来实现的。
/**
* corePoolSize: 线程池中核心线程的数量,因为是可缓存线程池,所以初始值为0
* maximumPoolSize:线程池中允许的最大线程数,因为是可缓存线程池,所以这个值是无限大。
* keepAliveTime:线程池中空闲线程的存活时间,即如果线程池中的线程空闲时间超过这个时间,就会被回收。
* unit:存活时间的时间单位。
* workQueue:任务队列,这里使用的是 SynchronousQueue,它是一个没有容量的阻塞队列,当有任务提交到线程池时,如果
* 没有空闲线程可用,就会直接创建一个新线程来执行任务。
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, // 核心线程数 0
Integer.MAX_VALUE, // 最大线程数 Integer最大值
60L, // 60秒
TimeUnit.SECONDS, // 秒
new SynchronousQueue());
}
newCachedThreadPool 适用于那些需要处理大量短时间任务,并且任务的数量和持续时间都不稳定的场景,可以实现高效的任务并发处理,并避免线程数量过多或过少导致的资源浪费和性能下降的问题。
Executors 类中的newCachedThreadPool() 方法,用于创建一个单线程池,该线程池中只有一个线程在处理任务。可以保证任务的顺序性和线程安全性。具体实现是通过 ThreadPoolExecutor 类来实现的。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, // 核心线程数 1
1, // 最大线程数 1
0L, // 存活时间 0
TimeUnit.MILLISECONDS, // 毫秒
new LinkedBlockingQueue()));
}
newSingleThreadExecutor 适用于那些需要保证任务顺序性和线程安全性,且任务量较少的场景。
Executors 类中的newScheduledThreadPool(int corePoolSize)方法,用于创建一个可调度的线程池,可以按照指定的时间间隔或者延迟来执行任务。具体实现是通过 ScheduledThreadPoolExecutor 类来实现的。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
/**
* corePoolSize: 线程池中线程的数量,即工作线程数。
* maximumPoolSize:最大线程数 Integer最大值
* keepAliveTime:0
* unit:毫秒。
* workQueue:任务队列,这里使用的是 DelayedWorkQueue,任务队列,这里使用一个延迟队列来存储任务,即任务按照延迟 * 时间排序,延迟时间短的任务先执行
*/
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
newScheduledThreadPool 适用于那些需要定期执行或延迟执行任务的场景,比如轮询任务、定时任务、任务队列等。