之前学习的并发知识,现在记录一下
主要参数说明
线程池(ThreadPoolExecutor),Jdk1.5版本开始使用,构造方法参数如下(来自ThreadPoolExecutor源码):
corePoolSize:核心线程数,线程池启动时就会创建的线程数量。即使核心线程是空闲的,也不会被回收,除非
调用了allowsCoreThreadTimeOut方法为true
executorService.allowCoreThreadTimeOut(true);
maximumPoolSize:最大线程数,线程池中最大的线程数量
keepAliveTime:线程超时时间,看源码可知,该参数的意义是线程从工作队列中取出任务的超时时间。其中,timed是指是否设置allowCoreThreadTimeOut为true,如果没有设置,则判断当前线程数是否大于核心线程数。timeout指的就是线程从队列中取任务是否超时。当一个线程从工作队列中取任务时,满足以下条件:(当前线程数大于最大线程数 或 (timed为true且该线程获取任务超时))&(当前线程数大于1 或 工作队列为空),这个线程就会被回收。
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
unit:超时时间的单位
workQueue:工作队列,详情见下面的说明
threadFactory:线程工厂,要实现ThreadFactory接口,线程池创建线程时会调用ThreadFactory的newThread方法创建线程。
RejectedExecutionHandler:饱和策略,详情见下面的说明
工作队列
工作队列和线程池创建线程的行为息息相关,下面就两种情况来讨论一下:
1.线程池设置了核心线程数(核心线程数不为0)
在调用excute方法提交任务后,会先判断当前线程池中的线程个数,如果当前线程数小于核心线程数,那么线程池就会创建一个新的线程,执行任务。否则,线程池会尝试将任务放入工作队列中,如果成功放入,且当前可用线程数为0,线程池就会创建一个线程,执行任务,否则使用线程池中存在的线程执行任务。如果放入工作队列失败(队列已满),并且当前线程数大于等于最大线程数,就不会再创建线程,使用饱和策略拒绝任务,否则就会创建一个线程,接手新提交的任务。
//获取当前线程数量
int c = ctl.get();
//判断线程数量是否小于核心线程数,小于则添加线程,新添加的线程会接手提交的任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
//添加线程失败,获取当前线程数,继续走下面的逻辑
c = ctl.get();
}
/*尝试将任务放入队列中*/
//放入队列成功
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//如果线程池关闭,拒绝任务
if (! isRunning(recheck) && remove(command))
reject(command);
//如果当前线程数为0,创建线程,但是新线程不接手当前任务,而是从队列中获取任务。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//放入队列失败,直接创建新的线程,接手任务。如果创建线程失败,拒绝任务
else if (!addWorker(command, false))
reject(command);
2.线程池没有设置核心线程数(核心线程数为0)
如果线程池没有设置初始线程,在调用excute方法后,会直接将任务放入工作队列,接下来的步骤和1中的一致,这里不再赘述
当一个线程执行完任务后,会再次从工作队列中取出任务执行。如果此时工作队列已经没有任务了,那么该线程就有可能按照超时时间的逻辑被回收,直到当前线程数和核心线程数一样时,不再回收线程,此时线程池还是处于运行状态,等待任务到来。但是,如果设置了allowCoreThreadTimeOut为true,那么所有线程都会被回收,线程池自动终止运行。
饱和策略
如果线程池中的线程数量大于等于最大线程数,且工作队列已满,新提交的任务就会被拒绝,此时就要使用饱和策略。默认饱和策略是AbortPolicy,该策略会抛出rejectedExecution,调用者可以捕获该异常,编写处理代码。除了默认的饱和策略,还有其他几种饱和策略:
>DiscardPolicy:抛弃策略,会悄悄的抛弃没法加入到队列的任务
>DiscardOldestPolicy:抛弃最旧策略,会悄悄的抛弃下一个要执行的任务。如果使用的是优先队列,会抛弃优先级最高的任务,最好不要使用。
>CallerRunsPolicy:调用者运行策略,即不会抛出异常,也不会放弃执行。任务会被调用了execute方法的线程调用。比如,在main方法中开启线程池,如果线程池使用该策略,那么饱和的任务会被主线程调用。
可以使用下面的代码设置饱和策略
testThreadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());