先想是不是,再问为什么!
上一篇文章中提到有关线程池的一些基本概念,本文将不再赘述,本文着力于使用代码来解读相关线程池知识。
虚拟机设置
线程池执行任务逻辑和线程池参数的关系
Executors创建线程池的方式
根据返回对象类型创建线程池可以分成以下几类:
创建返回ThreadPoolExecutor对象
创建返回ScheduleThreadPoolExcutor对象
创建返回ForkJoinPool对象
Executors创建返回ThreadPoolExecutor对象
newCachedThreadPool(创建可缓存的线程池)
创建线程参数:
核心线程数 corePoolSize: 0
最大线程数maximumPoolSize:Integer.MAX_VALUE
非核心线程空闲存活时间keepAliveTime:60
时间单位 unit: SECONDS
线程池所用缓冲队列workQueue:SynchronousQueue
线程默认工厂、默认处理器
SynchronousQueue
SynchronousQueue(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,newCachedThreadPool线程池使用了这个队列。
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
} static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
/**
* The default rejected execution handler
*/
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
//拒绝策略默认使用AbortPolicy,直接抛出RejectedExecutionException异常。
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}当一个任务提交时,不创建核心线程,创建非核心线程来处理;核心线程数量非常大,可当作无限创建线程,空闲非核心线程回收时间为60秒,在之前文章说明创建线程是需要消耗内存资源的,因此在有限的资源情况下是非常容易报OOM异常。
newSingleThreadExecutor(创建单线程的线程池)
测试创建线程参数:
核心线程数 corePoolSize: 1
最大线程数maximumPoolSize:1
非核心线程空闲存活时间keepAliveTime:0
时间单位 unit: MILLISECONDS
线程池所用缓冲队列workQueue:LinkedBlockingQueue
LinkedBlockingQueue
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}测试中设置1个核心线程,没有空闲时间,当一个任务提交时,有线程执行?有将任务放到阻塞队列:没有 创建一个线程,没有空闲时间,使用有界阻塞列表(可看作无限),执行完当前线程就从队列取任务,执行完一个,再继续取,(一条线程)夜以继日地干活。如果任务的执行比较耗时,将会导致队列的任务越积越多,效率不高。
newFixedThreadPool(创建固定长度线程池)
测试创建线程参数:
核心线程数 corePoolSize: 5
最大线程数maximumPoolSize:5
非核心线程空闲存活时间keepAliveTime:0
时间单位 unit: MILLISECONDS
线程池所用缓冲队列workQueue:LinkedBlockingQueue
测试中设置5核心个线程,没有空闲时间,当一个任务提交时,在已经创建核心线程执行,没有空闲时间,使用有界阻塞列表(可看作无限),如果任务的执行比较耗时,将会导致队列的任务越积越多,导致内存使用不停飙升, 最终导致OOM异常。
Executors创建返回newScheduledThreadPool对象
newScheduledThreadPool(定时及周期执行的线程池)
测试创建线程参数:
核心线程数 corePoolSize: 5
最大线程数maximumPoolSize:Integer.MAX_VALUE
非核心线程空闲存活时间keepAliveTime:0
时间单位 unit: NANOSECONDS
线程池所用缓冲队列workQueue:DelayQueue
DelayQueue
DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列,根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
测试中设置5核心个线程,没有空闲时间,当一个任务提交时,创建核心线程执行,执行当前线程从延时队列中取任务,线程从 延时队列中获取 time 大于等于当前时间的task,执行完后修改这个 task 的 time 为下次被执行的时间,如果当前核心线程已经满了,将任务放置到延时队列。
创建返回ForkJoinPool对象
暂时未曾使用过,待笔者使用后,将会补充这一空白区域!
小结
分析完上面四种可知:允许的任务队列长度为Integer.MAX_VALUE,就可能会有大量任务堆积在队列中,即而可能引起OOM异常;允许创建的线程数为Integer.MAX_VALUE,就可能会创建大量的线程,即而可能引起OOM异常;不设置多核心线程,一线程工作效率太低。
拒绝策略
AbortPolicy(抛出一个异常,默认的)
DiscardPolicy(直接丢弃任务)
DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
CallerRunsPolicy(交给线程池调用所在的主线程进行处理)
自定义拒绝策略,需要实现RejectedExecutionHandler接口
将创建返回ThreadPoolExecutor对象的线程池比作社区诊所、区卫生所、区医院,那么就可以很直观的发现;诊所只有一个医生,诊所内有一张长凳;区卫生所,有固定多个医生值班,卫生所内有一排排长椅;区医院门诊部,有多个医生值班,当病人很多时候还可以再请求科室医生下来再使用几个诊室,门诊候诊区外面有很多的长椅。当医院门诊部接收到医院内部通知,院内所有床位已用尽,等候区也已经饱和。这时开始执行拒绝措施:要么将问题不大的病人办理出院,空出位置;要么在门诊门口贴出告示,请病人到其他医院就诊,本医院已经饱和;要么直接不接收新的病人,让救护中心不要再送病人入院;要么暂时关闭门诊部,不再运行门诊,等到院内资源释放,再运作。
本文要是有遗漏的地方,我将在评论区给出,同时若读者发现本文不当之处也请在评论区指出,共同研究。