Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。线程池就是将线程进行池化,需要运行任务时从池中拿一个线程来执行,执行完毕,线程放回池中。
在开发过程中,合理地使用线程池能够带来3个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
ThreadPoolExecutor有四个构造函数,但最终调用的都是同一个:
public ThreadPoolExecutor(
int corePoolSize,//线程池核心线程数量
int maximumPoolSize,//线程池最大数量
long keepAliveTimet,//空闲线程存活时间
TimeUnit unit,//时间单位
BlockingQueue workQueue,//线程池使用的缓冲队列
ThreadFactory threadFactory,//线程池创建线程使用的工厂类
RejectedExecutionHandler handler//线程池对拒绝任务的处理策略
)
这里有一点需要注意是:当核心线程已满,队列未满的时候,这时新来的请求会加入队列等待;而此时假如队列恰好满了,这时新来的任务就会创建非核心线程执行任务。这样就会出现一个问题,前面来的任务还在队列中等待,而后来的任务却在非核心线程中得到执行!
Executors创建ThreadPoolExecutor对象的方法有三种:
Executors#newCachedThreadPool方法
public static ExectorService new CachedThreadPool(){
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
CachedThreadPool是一个根据需要创建新线程的线程池,当一个任务提交时,corePoolSize为0不创建核心线程,SynchronousQueue是一个不存储元素的队列,可以理解为队列永远是满的,因此最终会创建非核心线程来执行任务。非核心线程空闲60s后会被回收。
因为Integer.MAX_VALUE非常大,可以认为是可以无限创建线程的,在资源有限的情况下容易引起OOM异常
Executors#newSingleThreadExecutor方法
public static ExecutorService newSingleThreadExecutor(){
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlokingQueue()));
}
SingleThreadExecutor是单线程线程池,只有一个核心线程。当提交一个任务时,首先创建一个核心线程,再有就会放入队列,LinkedBlockingQueue是长度为Integer.MAX_VALUE的队列,基本可视为无界队列,所以无论maximumPoolSize和keepAliveTime参数是多少,都可视为无效,因为所有的任务都会放入队列中。在资源有限的时候容易引起OOM异常
Executors#newFixedThreadPool方法
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDES, new LinkedBlockingQueue());
}
它和SingleThreadExecutor唯一区别就是核心线程的数量!由于使用的是LinkedBlockingQueue,在资源有限的时候容易引起OOM异常
合理配置线程池大小
性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。
避免使用无界队列
不要使用Executors.newXXXThreadPool()快捷方法创建线程池,因为它们不是可创建线程太多就是队列可缓存太多线程,都可能造成OOM。所以最好是我们自己使用ThreadPoolExecutor的构造方法手动指定队列的最大长度。
常用阻塞队列有:
ExecutorService executorService = new ThreadPoolExecutor(2, 2,
0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(512), // 使用有界队列,避免OOM
new ThreadPoolExecutor.DiscardPolicy());