1、概述
由于频繁的创建和销毁线程会消耗很多资源,因此线程池应运而生来去除频繁的创建与删除线程这一过程。
2、常见线程池
①、newSingleThreadExecutor
单一线程池,使用唯一的工作线程执行任务,保证所有任务按照指定顺序执行。
ExecutorService service = Executors.newSingleThreadExecutor();
底层实现是FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
核心线程数和最大线程数都是1,存活时间为0,阻塞队列为LinkedBlockingQueue无界队列
②、newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过需要,可回收空闲线程,否则就创建新线程。
ExecutorService service = Executors.newCachedThreadPool();
底层返回的是ThreadPoolExecutor实例
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
核心线程数为0,当工作线程空闲超过60秒时工作线程将终止,终止后如果有新任务提交,则线程池会创建一个新的线程作为工作线程。
最大线程数为Integer.MAX_VALUE,由于使用的是SynchronousQueue同步队列,因此会在线程池中寻找空闲线程来执行任务,如果没有空闲线程则新建线程执行任务。
③、newFixedThreadPool
创建固定大小的线程池,可控制最大并发数,超出的任务会在队列中
ExecutorService service = Executors.newFixedThreadPool(5);
底层返回的也是ThreadPoolExecutor
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
核心线程数与最大线程数都是传进来的参数值大小,存活时间为0,采用无界队列。也就是说没提交一个任务,当工作线程数大于最大线程数时会将任务存到无界队列中,但是在线程池空闲时不会释放工作线程,还会占用一定的资源。
④、newScheduledThreadPool
创建一个指定大小的线程池,而且支持定时任务和周期任务
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
//两秒后每隔3秒执行一次
service.scheduleAtFixedRate(() -> System.out.println("123"),2,3,TimeUnit.SECONDS);
//延迟1秒后执行
service.schedule(() -> System.out.println("456"),1,TimeUnit.SECONDS);
其底层实际上还是使用了ThreadPoolExecutor。
底层创建了ScheduledThreadPoolExecutor实例
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
核心线程数为传进来的参数,最大线程数为Integer.MAX_VALUE,时间为0。大致来说会创建一个大小固定的线程池,存活时间无限制,支持定时和周期任务,如果所有线程都处于繁忙状态任务将进入DelayedWorkQueue
3、ThreadPoolExecutor
上面四种线程池对ThreadPoolExecutor进行了包装,方便使用,但有些情况下是直接使用ThreadPoolExecutor,通过自己设置参数来达到目的。
构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
-
corePoolSize,核心线程数,当有新任务提交时会进行下面的判断
- 如果当前线程数小于corePoolSize,则新建线程处理任务,即使此时有空闲线程也不使用;
- 如果当前线程数大于corePoolSize且小于maximumPoolSize,如果workQueue未满,则加入workQueue,否则新建线程处理此任务;
- 如果corePoolSize 和 maximumPoolSize相同,当workQueue未满时放入workQueue,否则按照拒绝策略处理新任务;
- 如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务。
-
workQueue
- 直接切换,这种方式常用的队列是SynchronousQueue,newCachedThreadPool使用的就是这种队列。
- 无界队列,基于LinkedBlockingQueue实现,此时maximumPoolSize不会起作用,它是一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue,Executors.newFixedThreadPool()使用了这个队列。
- 有界队列,基于ArrayBlockingQueue实现,可将线程池大小限制在maximumPoolSize
-
keepAliveTime,空闲时间等待超过这个keepAliveTime时间才会被回收
-
handler,拒绝策略,分为下面几种
- AbortPolicy,直接抛出异常,默认的拒绝策略;
- CallerRunsPolicy,使用调用者所在的线程执行任务;
- DiscardOldestPolicy,丢弃队列中最前面的任务,并将当前任务加入到队列中;
- DiscardPolicy,直接丢弃任务
线程池处理流程如下图
线程池生命周期
- RUNNING,能接受新任务,并且能处理阻塞队列中的任务;
- SHUTDOWN,关闭状态,不接受新提交的任务,但可以继续执行队列中的任务,在RUNNING调用shutdown()方法会转到此状态;
- STOP,不接受新任务,中断正在执行的任务,相当于调用了shutdownNow()方法;
- TIDYING,此状态所有任务都终止,有效线程为0,在此状态时会自动调用terminated()方法进入TERMINATED状态。
- TERMINATED,在执行terminated()后进入此状态,此状态什么也没做
线程池生命周期图
4、线程池是如何保证线程重复利用的
在execute()方法中调用了addWorker()方法,这是一个添加线程的方法,向线程池中添加的Runnable被包装成一个Worker对象,并在addWorker()方法里执行了start()方法,会调用Worker类的run()方法,Worker类的run方法又调用了runWorker()方法,runWorker()方法里面的while循环就是线程池中线程不被销毁的关键所在:while (task != null || (task = getTask()) != null) { }
,在Worker的run方法中,如果while一直执行下去,那么Worker这个继承了Runnable接口的线程就会一直执行下去,而我们知道线程池中任务的载体是Worker,如果Worker一直执行下去,就表示该载体可以一直存在,换的只是载体上我们通过execute()方法添加的Runnable任务而已。