目录
什么是线程池?
线程是操作系统进行资源分配和任务调度的最小单位,是计算机的宝贵资源,new thread(),调用start()方法java线程在创建和销毁的过程中消耗的资源是比较大的,为了减少在线程创建和销毁过程中的资源消耗就要实现线程的复用。在程序开发中池化技术是一种不错的复用方式,例如线程池、连接池、内存池等。我们会预先创建好一些昂贵的对象放入池中,在使用的时候直接获取,使用之后归还,还会通过一些策略调整池中对象数量实现池的动态伸缩。
怎么创建线程池?
java的executors类库中提供了多种快速创建线程池的方法,例如newFixedThreadPool(),newCachedThreadPool()等,但是我们尽量不要直接使用,《阿里java开发规约》中也直接规定了不能使用executors类库中的创建线程池方法,应该手动使用newThreadPoolExecutor来创建线程池。这是因为newFixedThreadPool(),newCachedThreadPool()创建的线程池都是无界的,在线程大量堆积的时候就会容易造成OOM,翻开newFixedThreadPool()的源码不难发现线程池的工作队列直接new了一个LinkedBlockingQueue,而默认构造方法的LinkedBlockingQueue是Integer.MAX_VALUE长度的队列,是无界的,线程可以无限的堆积。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
...
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
...
}
翻开newCachedThreadPool的源码会发现它的最大线程数是Integer.MAX_VALUE,而工作队列 SynchronousQueue 是一个没有存储空间的阻塞队列,只要有请求就必须找到一个线程来处理,如果没有空闲线程就新创建一个,无限的创建线程也容易造成OOM。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
根据具体的应用场景,我们需要清楚的知道我们所创建线程池的几个核心参数,包括:核心线程数、最大线程数、工作队列类型、拒绝策略、线程回收策略等以满足需求,一般需要可控的线程数和有界的工作队列。另外为了方便在线程池出现问题时(例如线程数暴增、线程死锁、占用大量CPU)方便定位我们需要为线程池定义一个有意义的名称。
ThreadPoolExecutor
构造方法参数:
public ThreadPoolExecutor(int corePoolSize, --核心线程数
int maximumPoolSize, --最大线程数
long keepAliveTime, --非核心线程数保留时间
TimeUnit unit, --keepAliveTime的时间单位
BlockingQueue<Runnable> workQueue, --工作队列
ThreadFactory threadFactory, --线程工厂
RejectedExecutionHandler handler --线程拒绝策略
)
参数说明:
1、corePoolSize:线程池核心线程数(平时保留的线程数)
2、maximumPoolSize:线程池最大线程数(当workQueue都放不下时启动新线程最大线程数)
3、keepAliveTime:超出corePoolSize数量的线程的保留时间。
4、unit:keepAliveTime单位
5、workQueue:阻塞队列,存放来不及执行的线程
ArrayBlockingQueue:构造函数一定要传大小
LinkedBlockingQueue:构造函数不传大小会默认为(Integer.MAX_VALUE ),当大量请求任务时,容易造成 内存耗尽。
SynchronousQueue:同步队列,一个没有存储空间的阻塞队列 ,将任务同步交付给工作线程。
PriorityBlockingQueue : 优先队列
6、threadFactory:线程工厂
7、handler:饱和策略
AbortPolicy(默认):直接抛弃
CallerRunsPolicy:用调用者的线程执行任务
DiscardOldestPolicy:抛弃队列中最久的任务
DiscardPolicy:抛弃当前任务
使用newThreadPoolExecutor创建线程池的示例:
//创建一个具有2个核心线程、5个最大线程,
//使用容量为10的ArrayBlockingQueue阻塞队列作为工作队列的线程池,
//使用默认的AbortPolicy拒绝策略
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2, 5,
5, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
new ThreadFactoryBuilder().setNameFormat("demo-threadpool-%d").get(),
new ThreadPoolExecutor.AbortPolicy());
线程池默认工作行为
1、不会初始化corePoolSize个线程,有任务来了才会创建线程。
2、当线程数超过corePoolSize时,不会先扩展线程数,而是将线程放在工作队列排队。
3、当工作队列中满了以后,扩展线程数最大到maximumPoolSize。
4、如果最大线程数达到maximumPoolSize以后还有线程进来按照拒绝策略处理。
5、超过核心线程数时,线程在等待keepAliveTime后仍没任务处理会被回收,线程池收缩至核心线程数。