首先,在Java的类库中,提供了几种常用的线程池类型。这些线程池类型都是通过java.util.concurrent.Executors类提供的静态方法创建的,可以根据实际需求选择合适的线程池类型来管理和执行任务。
但是,在《Alibaba Java开发手册》中有强制建议线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor方式创建。这样的处理方式能让编写代码的工程师更加明确线程池的运行规则,规避资源耗尽的风险。
Executors.newCachedThreadPool(缓存线程池):该线程池根据任务数量动态地创建线程,线程池的大小会根据任务的多少自动调整。
corePoolSize是0;
maximumPoolSize是Integer.MaxValue。如果达到这个上限,没有任何线程能够处理,肯定会抛出OOM异常;
BlockingQueue是SynchronousQueue。一种不存在任何元素的阻塞队列,也就是每提交一个任务到线程池,都会分配一个工作线程来处理;
keepAliveTime是60s。说明只要当前线程在60s内空闲则回收,如果有任务数增加,再次创建出新线程处理任务;
优点:根据任务数量动态地创建线程,可以适应不同负载的场景。提供了快速响应的能力。
缺点:如果任务数量过大,可能会创建过多线程导致资源消耗过高。
适用场景:适合执行大量短期的异步任务。
Executors.newScheduledThreadPool(定时线程池):是ScheduledExecutorService接口的实现类。该线程池可以按照固定的时间间隔或延迟执行任务。
maximumPoolSize是Integer.MaxValue。也会存在OOM风险。
BlockingQueue是DelayedWorkQueue。
优点:可以按照固定的时间间隔或延迟执行任务。提供了定时执行任务的能力。
缺点:任务的执行时间不可控,可能会受到其他因素的影响。
适用场景:适合需要定时执行任务的场景。
Executors.newFixedThreadPool(固定线程池):该线程池维护固定数量的线程,任务提交后会立即执行。如果所有线程都处于忙碌状态,任务会在队列中等待。
corePoolSize和maximumPoolSize是相等的。即输入的参数即是核心线程数也是最大线程数。
keepAliveTime是0,即不存在空闲线程。只要线程个数比核心线程个数多并且当前空闲则回收。
BlockingQueue是LinkedBlockingQueue(Integer.MaxValue)。这是无界队列,如果瞬间任务数非常大,会有OOM的风险。
优点:固定线程数量,避免了线程数量过多或过少的问题。提供了较好的线程复用性能。
缺点:当任务数量过多时,可能会导致任务在队列中等待执行。
适用场景:适合执行长期的、固定数量的任务。
Executors.newSingleThreadExecutor(单线程线程池):该线程池只包含一个线程,所有任务按照顺序执行。
corePoolSize和maximumPoolSize都是1。即核心线程个数和最大线程个数都为1的线程池。
keepAliveTime是0,即不存在空闲线程。只要线程个数比核心线程个数多并且当前空闲则回收。
BlockingQueue是LinkedBlockingQueue(Integer.MaxValue)。这是无界队列,如果瞬间任务数非常大,会有OOM的风险。
优点:保证任务按照顺序执行,避免了多线程环境下的并发问题。适合需要顺序执行任务的场景。
缺点:只有一个线程执行任务,无法并行处理多个任务。
适用场景:适合需要按顺序执行任务的场景,保证任务的可靠性。
Executors.newWorkStealingPool(工作窃取线程池):JDK 8引入,内部构建一个ForkJoinPool,创建持有足够线程来支持给定的并行度的线程池。该线程池使用多个队列,每个线程维护一个自己的队列。当一个线程完成自己队列中的任务后,会从其他线程的队列中窃取任务执行,因此构造方法中把CPU数量设置为默认的并行度。
优点:多个线程维护自己的任务队列,充分利用多核处理器的优势,提高并行处理能力。自动根据负载情况平衡任务的分配,提高任务执行效率。
缺点:对于需要共享数据的任务,需要进行额外的同步措施。
适用场景:适用于处理大量相互独立的任务,充分利用多核处理器的能力。