"Executors"和"ThreadPoolExecutor"都是Java中的并发工具类,都是用来创建和管理线程池的。
"Executors"是一个工厂类,它提供了创建线程池的静态方法,包括newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool等,这些方法可以创建不同类型的线程池。例如,newFixedThreadPool可以创建一个固定线程数的线程池,而newCachedThreadPool可以创建一个可以无限扩大的线程池。
然而,"Executors"的局限性在于它封装了创建线程池的方法,所以不能根据我们的使用情况来指定参数。如果需要更灵活的配置,可以使用"ThreadPoolExecutor"类。"ThreadPoolExecutor"提供了更多的参数,如核心线程数、最大线程数、空闲存活时间等,可以根据我们的需求来创建线程池。
总的来说,"Executors"使用方便,但灵活性较差,而"ThreadPoolExecutor"虽然使用相对复杂,但灵活性更高。
ThreadPoolExecutor 源码的一个构造方法
Executors 工厂类使用的就是该方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
corePoolSize:核心线程数,即线程池中始终保持运行的线程数量。
maximumPoolSize:最大线程数,即线程池中允许同时运行的最大线程数量。
keepAliveTime:空闲存活时间,即非核心线程在没有任务执行时,保持存活的最长时间。
unit:空闲存活时间的单位,如秒、分钟等。
workQueue:工作队列,用于存储等待执行的任务。
defaultThreadFactory():默认线程工厂,用于创建线程。
defaultHandler:默认拒绝策略,当线程池中的线程数达到最大线程数,并且工作队列已满时,线程池会拒绝新任务,此时会调用拒绝策略。
如何处理被拒绝的任务
四个官方的的拒绝策略 | 工作方式 |
---|---|
ThreadPoolExecutor.AbortPolicy() | 任务队列满后续的直接抛弃,并抛出异常RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()) |
ThreadPoolExecutor.DiscardPolicy() | 任务队列满后续的直接抛弃,但是不抛异常 |
ThreadPoolExecutor.DiscardOldestPolicy() | 抛弃队头的任务,新任务队尾进,不抛异常 |
ThreadPoolExecutor.CallerRunsPolicy() | 线程池内的线程都在忙,那就让主线程来执行新任务,同时执行一个简单的反馈控制机制,降低新任务提交的速度 |
代码如下(示例):
public class Main {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// newCachedThreadPool 内部实现如下(使用默认的 AbortPolicy)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
0,// 核心线程数为 0
Integer.MAX_VALUE,// 最大线程数为 0x7fffffff
60L,//
TimeUnit.SECONDS,// 非核心线程存活时间为 60 秒
new SynchronousQueue<Runnable>()// 工作队列
);
}
}
SynchronizedQueue 一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。(即必须进一个出一个,进去的不出去就卡住)
同时因为 SynchronizedQueue 没有容量,而且maximumPoolSize(最大线程数)为Integer.MAX_VALUE,所以如果任务很多会不断地创建非核心线程,导致 OOM(内存溢出)
即任务过多会导致非核心线程不断创建,Executors.newCachedThreadPool出现OOM。
代码如下(示例):
public class Main {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// newFixedThreadPool 内部实现如下(使用默认的 AbortPolicy)
ThreadPoolExecutor executor1 = new ThreadPoolExecutor(
5,// 核心线程数为 5
5,// 最大线程数为 5
0L,//没有非核心线程数
TimeUnit.MILLISECONDS,//
new LinkedBlockingQueue<Runnable>()
);
}
}
LinkedBlockingQueue 如果不指定容量,默认的容量是 Integer.MAX_VALUE,如果设置的核心线程过少,任务又很多,线程都在忙碌,而此时默认的拒绝策略是 AbortPolicy,任务就会不断地添加到该阻塞队列,最终导致OOM。
即任务过多线程太少会导致工作队列不断变大, Executors.newFixedThreadPool 出现OOM。
代码如下(示例):
public class Main {
public static void main(String[] args) {
// 等同于 Executors.newFixedThreadPool(1)
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// Executors.newSingleThreadExecutor 内部实现如下(使用默认的 AbortPolicy)
ThreadPoolExecutor executor2 = new ThreadPoolExecutor(
1,// 核心线程数为 1
1,// 最大线程数为 1
0L,// 没有非核心线程
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()
);
}
}
见名知意,该方法仅仅创建一个线程,工作队列为 LinkedBlockingQueue,拒绝策略为AbortPolicy,所以会和Executors.newFixedThreadPool 出现相同的问题。
同理,即任务过多线程太少会导致工作队列不断变大,Executors.newSingleThreadExecutor 出现OOM。