主要处理流程:java
(1)判断核心线程池是否已满,若是还没满则建立线程执行任务,不然进入下一步数据库
(2)判断工做队列是否已满,若是没满则将该任务放入工做队列等待线程来执行,不然进入下一步编程
(3)判断线程池中的线程是否都处于工做状态,若是不是则新建立一个线程来执行任务,没有处于工做状态的线程被淘汰,不然按照饱和策略处理该任务。并发
1:当一个任务提交时,若是CorePool
中的核心线程少于CorePoolSize
,则建立一个新线程执行任务(须要全局锁)源码分析
2:若是CorePool中没有空闲的线程,那么加入BlockingQueue等待核心线程拉取任务执行性能
3:若是BlockingQueue已满,建立新线程后若是大于maximumPoolSize
就跳转到4拒绝执行任务,若是小于就建立新线程执行任务(须要全局锁)ui
4:四种不一样的拒绝策略,经过rejectedExecution()
方法执行。spa
须要获取全局锁致使线程池的性能大大降低,应该尽可能避免产生步骤1和步骤3线程
public void execute(Runnable command) {
// 若是任务为空则抛出异常
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 1.工做线程数小于核心线程池大小则添加工做线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
// 更新线程池工做状态
c = ctl.get();
}
// 2.判断线程池是否处于工做状态,若是是则尝试把任务放入阻塞队列中
if (isRunning(c) && workQueue.offer(command)) {
// 3.再次检查是否应该回滚任务添加线程,防止任务放入阻塞队列后线程池down或者工做线程死亡
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
//double check时线程池down了,该任务没法执行
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 4.没法加入阻塞队列或者线程池已down,拒绝任务执行
else if (!addWorker(command, false))
reject(command);
}
复制代码
1: execute()方法建立一个Worker
线程执行当前任务rest
2:若是Worker
线程数目等于CorePoolSize
,则加入到阻塞队列中,等待Worker
取出来执行任务
3:若是BlockingQueue
已满,若是Worker
线程数小于maximumPoolSize
则建立新线程执行任务
4:反复执行(1)(2)(3)
重点是设置参数,参数设得好,线上没烦恼
围绕下面几个问题思考如何设置线程池
线程池的大小?例如在
2G
内存里配置一个线程池,如何设置线程池大小执行的任务属于哪种类型?例如IO密集、CPU密集······
任务是否具备优先级?例如任务的紧急程度、执行时间长短
任务是否具备依赖性?例如依赖数据库链接
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler);
复制代码
每次提交任务时,若是当前线程数小于corePoolSize
就会建立一个新线程,即便线程池中有空闲线程,直到线程数等于corePoolSize
。
能够提早建立好全部线程并启动,调用prestartAllCoreThreads()
。
保存等待执行的任务队列,可使用任意阻塞队列,例如ArrayBlockingQueue
, LinkedBlockingQueue
, PriorityBlockingQueue
, SynchronousQueue
线程池容许的最大线程数,阻塞队列中的任务已满(队列必须有界)时,下一个任务没法进入阻塞队列,若是线程池中的线程数小于最大线程数,则尝试在线程池中继续建立线程执行任务
给每一个线程设置更有意义的名字的一个工厂,例如ThreadFactoryBuilder
工厂
任务拒绝执行时采用的拒绝策略,有四种:
AbortPolicy
:抛出异常CallerRunsPolicy
:使用调用者线程执行任务DiscardOldestPolicy
:丢弃队列中最近的一个任务,并执行当前任务DiscardPolicy
:直接丢弃线程池的工做线程空闲后能够保持存活的时长。若是任务不少且每一个任务的执行时间很短,能够调大存活时长提升线程利用率。
DAYS
/HOURS
/MINUTES
/MILLISECONDS
(毫秒)/MICROSECONDS
(微妙)/NANOSECONDS
(纳秒)
两个核心方法:有返回值的submit()
和无返回的execute()
线程池会返回一个Future
对象,对象中存储着任务是否执行成功、返回结果等信息,调用get()
方法能够获取返回值,在任务未完成前会一直阻塞,能够设置超时时长get(long, TimeUnit)
防止一直阻塞。
Future<Object> future = executor.submit(hasReturnValueTask);
try {
future.get();
} catch(Exception e) {
} finally {
// 关闭线程池
executor.shutdown();
}
复制代码
两个核心方法:shutdown()
和shutdownNow()
区别:
shutdownNow()
会将线程池状态设置为STOP
,而后尝试终止全部正在执行任务的线程,并返回等待任务的任务列表
shutdown()
会将线程池状态设置为SHUTDOWN
,而后中断全部没有正在执行任务的线程。
如何选择:
若是任务不必定执行完,可使用shutdownNow()
,不然使用shutdown()
调用关闭方法后,调用isShutdown()
方法就会返回True
,可是确认线程池的关闭还须要调用isTerminated
方法,这个方法表示全部的任务都已经关闭了。
if(isShutdown() && isTerminated()) {
System.out.print("线程池已关闭!");
}
复制代码
分析任务特性:
任务的性质:CPU密集、IO密集、混合型(配置线程数)
任务的优先级:高、中和低(配置阻塞队列)
任务的执行时间:长、中和短(配置线程空闲存活时长)
任务的依赖性:是否依赖其余系统资源,如数据库链接(配置线程数目)
重点说明任务的依赖性如何肯定线程数目,若是线程须要依赖数据库链接,提交SQL后须要等待数据库返回结果,这个过程CPU是空闲的,CPU空闲时间越长,那么线程数应该设置得越大,能够更好地利用CPU。
最好配置阻塞队列会有界阻塞队列,能够监控线程池的工做状态,若是大量的任务放入阻塞队列则会不断报出任务拒绝信息。
takeCount
:线程池须要执行的任务数量
completedTaskCount
:线程池在运行过程当中已完成的任务数量
largestPoolSize
:线程池曾经建立过的最大线程数量
getPoolSize
:线程池的线程数量
getActiveCount
:获取活动的线程数目
能够经过扩展线程池进行监控,重写beforExecute()
、afterExecute()
和terminated()
方法。
到这里为止,应该要掌握下面的知识,若是你能很流畅地回答,那么恭喜你,线程池的基本原理算是过关了
总结不易,若是阅读完这篇文章对你有小小的收获,你的一个小小的点赞能让我高兴一成天!
巨人的肩膀:
《Java并发编程的艺术》