1、ThreadPoolExecutor
ThreadPoolExecutor运行状态有五种:
- RUNNING: 接受新的任务,并处理队列任务。(Accept new tasks and process queued tasks )
- SHUTDOWN: 不接受新的任务,但是会处理队列中的任务。(Don't accept new tasks, but process queued tasks )
- STOP: 不接受新的任务,不处理队列任务,并且会中断正在运行的任务。 (Don't accept new tasks, don't process queued tasks,and interrupt in-progress tasks )
- TIDYING:当所有任务已终止,workerCount为零的时候,运行状态会变为TIDYING,继而运行terminated() 方法。(All tasks have terminated, workerCount is zero,the thread transitioning to state TIDYING will run the terminated() hook method)
- TERMINATED: terminated() 方法已调用完成。(terminated() has completed)
上面是java源码上的注释,再来看一下运行状态是如何转换的。
RUNNING -> SHUTDOWN:需要调用shutdown()方法,或者调用finalize(),finalize里面会调用shutdown()
(RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()
SHUTDOWN -> TIDYING:线程池为空、任务队列为空的时候,自动转换状态
STOP -> TIDYING:线程池为空,shutdownNow()把任务都干掉了,线程池清空后自动转换
TIDYING -> TERMINATED:terminated()方法完成后。线程池为空、任务队列为空,TIDYING状态确保两者都为空后,线程池被设置终止状态。
如下图
当调用shutdown()方法时,线程池不再接受新任务,但会把原有任务执行完毕。boolean awaitTermination(long timeout, TimeUnit unit)方法可以监听任务执行完毕,使用方法如下
//等待线程池内任务执行完毕
threadPoolExecutor.shutdown();
//1秒检查一次
while(!threadPoolExecutor.awaitTermination(1,TimeUnit.SECONDS)) {
}
或者使用FutureTask的get方法等待结果返回。不过这两种方法都很局限,awaitTermination需要全部线程执行完毕,FutureTask需要任务有返回值。为解决等待子任务执行后再去操作的问题,JDK封装了同步器让开发者更好地使用。
2、类结构图
3、ThreadPoolExecutor构造参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) ;
corePoolSize :线程池核心线程个数。初始化时不创建,有任务才创建,后面会保持核心线程运行。
maximunPoolSize : 线程池最大线程数量。maximunPoolSize-corePoolSize=非核心线程数,非核心线程会在空闲的keeyAliveTime时间后被回收。
keeyAliveTime :存活时间。如果当前线程池中的线程数量比核心线程数量多,并且是闲置状态, 则这些闲置的线程能存活的最大时间。
TimeUnit : 存活时间的时间单位。
workQueue :用于保存等待执行的任务的阻塞队列,比如基于数组的有界ArrayBlock ingQueue 、基于链表的无界LinkedBlockingQueue 、最多只有一个元素的同步队列SynchronousQueue 及优先级队列PriorityB lockingQueue 等。
ThreadFactory :创建线程的工厂。
RejectedExecutionHandler :饱和策略, 当队列满并且线程个数达到maximunPoolSize后采取的策略, 比如AbortPolicy (默认策略,抛出异常〉、CallerRunsPolicy (使用调用者所在线程来运行任务) 、DiscardOldestPolicy (调用poll 丢弃一个任务,执行当前任务)及DiscardPolicy (默默丢弃,不抛出异常〉。这四个策略都是ThreadPoolExecutor的内部类。
注意:假如corePoolSize为10,maximunPoolSize为20(20-10=10个非核心线程),workQueue是长度为15的LinkedBlockingQueue。这时有50个任务进来,首先核心线程会处理10个,15个会塞满等待队列,接下来才会开辟新的线程(非核心线程)处理10个。若所有线程都满且还在处理,接下来的任务会触发线程池饱和策略。这里要注意的是,由于非核心线程的存在,使得中间加入的任务可能会比后面加入的更加晚执行。
4、线程池类型
1)newFixedThreadPool :创建一个核心线程个数和最大线程个数都为nThreads 的线程池,并且阻塞队列长度为Integer.MAX_VALUE。keepAliveTIme=0说明线程个数比核心线程个数多并且空闲时,则立马回收多出的线程。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
这里使用的是LinkedBlockingQueue无参构造函数,队列最大长度为Integer. MAX_VA LUE(2^31-1),可能会导致OOM。
2)newCacheThreadPool :创建一个按需创建线程的线程池,初始线程个数为0,最多线程个数为Integer. MAX_VA LUE ,并且阻塞队列为同步队列。keepAliveTIme=60说明只要当前线程在60s内空闲则回收。这个类型的特殊之处在于, 加入同步队列的任务会被马上执行,同步队列里面最多只有一个任务。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
由于线程个数最大为Integer. MAX_VA LUE(2^31-1),可能会导致线程过多,cpu100%,也会导致OOM。
3)newSingleThreadExecutor : 创建一个核心线程个数和最大线程个数都为1 的线程池,并且阻塞队列长度为Integer.MAX_VALUE 。keepAliveTIme=0说明只要线程个数比核心线程个数多并且当前空闲则回收。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
这里使用的是LinkedBlockingQueue无参构造函数,队列最大长度为Integer. MAX_VA LUE(2^31-1),可能会导致OOM。
4)比较好的线程池开启方法
最好不要直接上面三个juc包下Executors类自带的线程池类
ExecutorService executor = new ThreadPoolExecutor(2,2,
0,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(2),
new UserThreadFactory("groupName"));
/**
* 线程工厂
* @author :GG
* @date :Created in 2021/7/3 21:57
*/
public class UserThreadFactory implements ThreadFactory {
private final String namePrefix;
private final AtomicInteger nextId = new AtomicInteger(1);
// 定义线程组名称,在jstack问题排查时,非常有帮助
public UserThreadFactory(String whatFeaturOfGroup) {
namePrefix = "From UserThreadFactory's " + whatFeaturOfGroup + "-Worker-";
}
@Override
public Thread newThread(Runnable task) {
String name = namePrefix + nextId.getAndIncrement();
Thread thread = new Thread(null, task, name, 0);
System.out.println(thread.getName());
return thread;
}
}