- 1、什么是线程池
这里先简述一下,要执行一个线程(任务)时,不是new一个线程,而是从线程池获取一个存在的线程,执行完毕后,把该线程归还到线程池,等待下次执行任务。
- 2、线程池的原理
线程池是一个对象池。对象池是一种常见的系统优化技术。其核心思想是,一个类被频繁使用时,没必要每次都初始化一个对象,可以将它的一些实例保存在一个容器中,使用时向容器请求,用完后归还容器,达到复用对象的目的,减少对象创建和销毁时的系统消耗。这里的容器就是对象池。线程池本质上是一个线程对象的对象池,由于对象的创建和销毁会有一定的耗时,使用线程池复用线程有利于提高系统性能。
对象池的使用场景:一些大型对象被频繁调用,大型对象指创建或销毁比较耗时的对象
常见的对象池有:线程池,数据库连接池。
- 3、线程池的好处
1)降低资源消耗
从对象池的知识我们知道,线程池降低了线程创建和销毁的资源消耗,特别是一些耗时耗资源对象创建,比如数据库连接对象。
2)提高响应速度
线程池减少了创建和销毁线程池的时间,提高了响应速度
3)便于管理线程
线程池提供了关闭线程池、指定核心线程数等操作,方便了管理线程。
- 4、默认实现
JAVA给我们定义好了几种常见的线程池:
1)生成固定线程数的线程池
Executors.newFixedThreadPool(int nThreads)
2)生成一个可缓存的线程池
Executors.newCachedThreadPool()
3)生成单线程的线程池
Executors.newSingleThreadExecutor()
- 5、线程池(Exector架构)的类图
核心类是ThreadPoolExecutor类,继承AbstractExecutorService抽象类(写了线程池的一些默认实现),AbstractExecutorService实现了ExecutorService接口(该接口定义了线程池的操作关闭、立即关闭、提交等操作),ExecutorService接口继承自Executor接口(定义execute方法)。
Executors类,在这里是工厂类。
- 6、ThreadPoolExecutor类构造方法参数(引用自参考1)
- corePoolSize:线程池中的核心线程数;
- maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程;
- keepAliveTime:线程池中非核心线程闲置超时时长(准确来说应该是没有任务执行时的回收时间);
一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉
如果设置allowCoreThreadTimeOut(boolean value),则也会作用于核心线程
- TimeUnit:时间单位。可选的单位有分钟(MINUTES),秒(SECONDS),毫秒(MILLISECONDS) 等;
- workQueue:任务的阻塞队列,缓存将要执行的Runnable任务,由各线程轮询该任务队列获取任务执行。可以选择以下几个阻塞队列。
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处 于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
- ThreadFactory:线程创建的工厂。可以进行一些属性设置,比如线程名,优先级等等,有默认实现。
- RejectedExecutionHandler:任务拒绝策略,当运行线程数已达到maximumPoolSize,队列也已经装满时会调用该参数拒绝任务,默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。
AbortPolicy:直接抛出异常。
CallerRunsPolicy:只用调用者所在线程来运行任务。
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉。
当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
- 7、ThreadPoolExecutor执行过程(参考文献2)
corePoolSize->workQueue->maxPoolSize
1) 当未超过核心线程数时,直接创建一个核心线程去执行任务
2) 当超过核心线程数时,将任务加入workQueue中等待执行
3) 当workQueue满时,在不超过maxPoolSize的情况下,启动线程去处理任务
4、当线程数量超过maxPoolSize时,调用拒绝策略
以下是流程图:
8、理解默认线程池实现
1) FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
FixedThreadPool,创建固定核心线程数的线程池。提交任务时,nThread<corePoolSize(corePoolSize=maxPoolSize),则创建核心线程,否则加入工作队列等待执行;当工作队列满时,则执行拒绝策略。
2)CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
CachedThreadPool,corePoolSize=0,maxPoolSize=MAX_Value。提交任务时,nThread永远>corePoolSize,同时SynchronousQueue是一个不存储元素的阻塞队列,所以CachedThreadPool提交任务时会尝试获取线程,若获取到则执行,若获取不到,则创建线程执行任务。任务执行完毕后,线程归还线程池,在无调用的情况下,存活时间是60s。
3)SingleThreadPool
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
SingleThreadPool,corePoolSize=1,maxPoolSize=1。该线程池只有一个核心线程,最多允许一个线程。若任务正在执行,新提交的任务将会进入阻塞队列等待执行。
- 9、配置线程池大小
1)CUP密集型任务,就需要压榨CUP,N=Ncpu+1
2)IO密集型任务,参考值可为 N=2xNcpu
以上仅是参考值,最佳大小需要自己调试。
参考文献:
文献1:https://blog.csdn.net/u010983881/article/details/79322499
文献2:https://www.jianshu.com/p/e66e9924a953
文献3:https://www.cnblogs.com/dolphin0520/p/3932921.html
文献4:《JAVA程序性能优化》-葛一鸣