线程池在Java多线程中应用也很多,面试中出现频率比较高。今天来整理下线程池相关的知识点。总的来说使用线程池管理有以下几个好处:
- 减小时间开销。 如果每次使用单独创建线程,会有创建和销毁的时间损耗,而在线程池中的线程可实现复用。
- 减少等待时间。 在需要使用线程时直接从线程池调取即可,避免了等待创建的过程。
- 统一管理。 避免资源消耗过度。使用线程池统一管理,控制最大创建线程数。
Executors
先来说说Executors类,由于创建线程池较复杂,有很多参数需要设置。Executors就类似于游戏中的【新手上路】,会帮你默认配置好一些参数,更轻松的创建线程。
newFixedThreadPool
newFixedThreadPool方法用于创建固定线程数量的线程池,多余任务在队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
newSingleThreadExecutor
newSingleThreadExecutor创建只有一个线程的线程池。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newCachedThreadPool
该方法不限制最大线程数量。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ThreadPoolExecutor
其实看了上面几种Executors类的调用方式,大概可以发现:其内部是调用的ThreadPoolExecutor。这个类只是帮你根据不同场景设置了一些默认情况下可能较优的参数。但是其实更好的方式还是程序员手动调用ThreadPoolExecutor,这样才能更好的的控制线程数量。阿里的Java开发手册就明确限制了不要使用Executors。
ThreadPoolExecutor需要传入的参数如下:
corePoolSize:
核心线程数。
maximumPoolSize:
最大线程数。
keepAliveTime:
最大闲置时间。
unit:
时间单位。
workQueue:
任务阻塞队列。
下面介绍下使用线程池的整个流程:
- 第一个任务进来时,线程数量为0,小于核心线程数量,因此创建线程;
- 后面任务进来时都会依次创建线程,直到线程数到达核心线程数;
- 继续有任务加入,不再创建线程,而是把任务压入任务队列;
- 如果有任务继续进入,任务队列也满了,则会创建新的线程;
- 创建线程的最大数量为maximumPoolSize,到了该数量则不再创建线程(多余任务抛弃);
- 如果任务后续减少了,有超出核心线程数的部分闲置了,则当闲置时间大于keepAliveTime时,回收该线程;
- 核心线程数即使闲置了也不会回收;
注意:使用prestartAllCoreThreads()
函数可以预启动所有核心线程。
总结
刚上手时或者自己平时使用可以用Executors,比较简单。但是实际项目中最好还是遵循规范,使用ThreadPoolExecutor创建线程池。