今天看到关于线程池的一篇帖子,是关于面试时问到ThreadPoolFactory构造器时的一些问题,之前博主也学习过一些关于ThreadPoolFactory构造器的问题,但是一直没有总结过,既然今天有时间,那么就总结一下,避免有些同学走弯路(有些工作多年的老鸟也不一定能准确的说明coreSize,MaxSize,workQueueSize的关系),话不多说上干货。
下面来直接看ThreadPoolFactory最详细的构造器结构
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
从上面的代码块可以看出来在初始化时,对构造参数做了大小比较,一旦下面比较有一个为真就会抛出异常
corePoolSize<0
maximumPoolSize<=0
maximumPoolSize
从构造参数的入口可以看出主要的几个参数一共有如下几个
int corePoolSize
int maximumPoolSize
long keepAliveTime
TimeUnit unit
BlockingQueue workQueue
ThreadFactory threadFactory
RejectedExecutionHandler handler
而一般最让人迷糊的3个参数就是corePoolSize,maxmumPoolSize,workQueueSize
下面就针对这几个参数做下总结:
当线程池初始化时,PoolSize为0,当有任务向线程中提交时,线程池开始创建线程,用来执行业务。
JobCount代表向线程池中添加的任务数
1.JobCount <=corePoolSize
2.corePoolSize <JobCount <=workQueueSize
3.corePoolSize
4.corePoolSize
(当JobCount大于corePoolSize并且大于workQueueSize和maxmumPoolSize之和。那么线程池将会调用handler.rejectedExecution方法)
上面说了corePoolSize,maxmumPoolSize,workQueueSize 3个参数的用法了,那么接下来说一下RejectedExecutionHandler这个参数的意思。
从字面上理解为拒绝处理者,可以理解为当任务数大于maxmumPoolSize后的一个回调方法,RejectedExecutionHandler本身是一个接口,jdk本身对他有4个实现类
CallerRunsPolicy //线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度
AbortPolicy(如不指定handler则默认这个) //处理程序遭到拒绝将抛出运行时RejectedExecutionException;
DiscardPolicy //不能执行的任务将被删除
DiscardOldestPolicy //如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)
TimeUnit参数很好理解,是一个用来当做单位的枚举。主要还是说说keepAliveTime这个参数,网上很多对这个参数都是几句话带过,说是用来控制线程回收时间的参数,确实他的作用就是这样,但是看下面例子
public class Test {
public static void main(String[] args) {
BlockingQueue queue = new ArrayBlockingQueue<>(1);
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1, 5, 5, TimeUnit.SECONDS, queue);
System.out.println("pool初始化时PoolSize大小:"+poolExecutor.getPoolSize());
System.out.println("pool初始化时active大小:"+poolExecutor.getActiveCount());
poolExecutor.execute(new Test().new Task("1"));
poolExecutor.execute(new Test().new Task("2"));
poolExecutor.execute(new Test().new Task("3"));
poolExecutor.execute(new Test().new Task("4"));
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("pool现在PoolSize大小:"+poolExecutor.getPoolSize());
System.out.println("pool现在active大小:"+poolExecutor.getActiveCount());
// poolExecutor.shutdown();
}
class Task implements Runnable {
String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
int i=0;
while (true) {
i++;
if (name.equals("4")) {
if (i==5) {
break;
}
}
if (name.equals("2")) {
if (i==2) {
break;
}
}
System.out.println(name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
例子看上去不是太明白,但是keepAliveTime本身就不是很容易触发
1.keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。
2.反之,如果核心数较小,有界BlockingQueue数值又较小,同时keepAliveTime又设的很小,如果任务频繁,那么系统就会频繁的申请回收线程。
上面的例子总结起来就是
1.假设corePoolSize=1,QueueSize=1,MaxmumPoolSize=5
2.当初始化线程池的时候CorePoolSize为0,假设这个时候来了4个任务为t1、t2、t3、t4
3.那么线程池会创建一个线程用来执行t1
4.接下来发现coreSize不足,那么将t2放到Queue中
5.又发现QueuePool的大小不够了,判断MaxSize大小没有超出,那么接下来申请线程执行t3和t4
6.,执行了一会发现t4执行完毕了,(PS:注意了)这个时候不管keepaliveTime不管设置的多小,也不会回收的,因为QueuPool中还有一个t2没有执行,那么现在就会拿刚才用来执行t4的线程执行t2,执行的时候发现t2执行的很快,那么现在线程池的状态就是一共有3个线程,两个属于active的,那么这个时候就靠keepAliveTime参数判断回收时间了。
可以将TimeUnit的单位修改为NANOSECONDS比较两次的输出,就会明白了