线程池的产生是为了避免重复的创建线程和回收线程。
创建线程池的优点:
1、降低资源消耗。通过重复利用已创建的线程降低线程创建,销毁线程造成的消耗。
2、提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
3、提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
在java线程池中的newFixedThreadPool,newSingleThreadExecutor,newCachedThreadPool,newSingleThreadScheduleExecutor,newScheduledThreadPool这五个线程池在底层都是调用了ThreadPoolExecutor()这个构造方法。若Executors这个类无法满足需求的时候,可以自己创建自定义的线程池。
ThreadPoolExecutor类的定义如下:
public ThreadPoolExecutor(int corePoolSize,//核心线程数--线程池初始化创建的线程数量
int maximumPoolSize,//最大线程数,线程池中能创建的最大线程数
long keepAliveTime,//线程存活时间
TimeUnit unit,//线程存活的时间单位
BlockingQueue<Runnable> workQueue,//一个阻塞队列
ThreadFactory threadFactory//拒绝策略
) {……}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
(1)执行流程:
(2)适用场景:newFixedThreadPool适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
只有一个线程的线程池,多出来的任务会被放到任务队列内,待线程空闲,按先入先出的顺序执行队列中的任务。使用LinkedBlockingQueue队列(无界队列)。
(1)执行流程:
(2)适用场景:用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newCachedThreadPool使用的队列是SynchronousQueue,这个队列的作用是传递任务,并不会保存任务。因此当提交任务的速度大于处理任务的速度时,每次提交一个任务,就会创建一个线程。极端情况下会创建过多的线程,耗尽CPU和内存资源。
(1)执行流程:
(2)适用场景:用于并发执行大量短期的小任务,或者是负载较轻的服务器。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
DelayedWorkQueue队列的长度具有增长型和按超时时间升序排序算法,使得ScheduledThreadPoolExecutor具有延时执行任务的特性。
(1)执行流程:
(2)适用场景:用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。
JDK给我们提供了一些拒绝策略:
如果需要自定义策略,需要实现RejectedExecutionHandler接口。