线程池和数据库连接池的理念很相似,对于数据库连接池:普通的连接数据库是建立一个JDBC连接,执行完sql之后,就会关闭,即销毁connection对象,再次连接还需要重复上述步骤。当与数据库交互频繁时,这种模式会严重影响程序的性能,因此有了数据库连接池。对应到线程池thread pool,就是线程池里维护着多个线程,等待监督管理者分配执行任务。线程池带来的好处就是:
关于线程切换的例子:10 年前单核 CPU 电脑,假的多线程,像马戏团小丑玩多个球,CPU 需要来回切换。 现在是多核电脑,多个线程各自跑在独立的 CPU 上,不用频繁切换,效率高。
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executors(工具类)、ExecutorsExecutorService、ThreadPoolExecutor这几个类

线程池有以下几类:
public class ThreadPoolDemo {
public static void main(String[] args) {
//一池五线程
ExecutorService threadPool = Executors.newFixedThreadPool(3);
//一池1线程
ExecutorService threadPool1 = Executors.newSingleThreadExecutor();
//一池可扩容线程
ExecutorService threadPool2 = Executors.newCachedThreadPool();
//提交10次任务到线程池
try{
for (int i = 1; i <= 20; i++) {
//提交任务到另一线程(线程池中的)
threadPool2.execute(() -> {
System.out.println(Thread.currentThread().getName() + "线程正在办理业务");
});
}
}catch(Exception e){
e.printStackTrace();
}finally{
threadPool2.shutdown();
}
}
}
以可扩容线程池为例:


如图,此时常驻线程数为2,最大线程数为5,阻塞队列长度为3(黑点),此时来了两个任务1和2 ⇒ 直接常驻线程 ⇒ 那两个任务还执行完,又来了几个任务3、4、5 ⇒ 这时不是直接上最大线程,而是进入阻塞队列 ⇒ 此时又来了三个人6、7、8 ⇒ 发现阻塞队列也满了,那就开启最大线程处理6、7、8的业务 (注意新开的线程不是去处理阻塞队列了,阻塞队列的3、4、5还是在队列中继续等待) ⇒ 此时又来了一个任务9 ⇒ 走拒绝策略
注意这几点:
ExecutorService pool = Executors.newSingleThreadExecutor();
看下源码,从提交任务的execute方法打断点,进入execute方法:


阻塞队列和最大线程数量都用完后,走拒绝策略,JDK内置的拒绝策略有:
查看源码可以发现,不管是三种线程池中的哪种,最后都是return new ThreadPoolExecutor,关于ThreadPoolExecutor类:

以银行为例对比:银行大厅一共有10个窗口(最大线程数量),但平时一般只开5个(常驻线程数量),某天办理业务的人很多,5个窗口不够用,其余人来了就先在大厅椅子上坐着等(阻塞队列),结果椅子坐满了,还有人陆续来,于是10个窗口全开,还来很多人,那就只能告诉新来的今天轮不到你办了(拒绝策略)。
Executors工具类可以创建三种线程池,但通常自定义线程池是因为,Executors返回的线程池对象有以下两个问题:
public class ThreadPoolDemo2 {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2, //常驻或核心线程数
5, //最大线程数
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3), //阻塞队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() //拒绝策略
);
try {
for (int i = 1; i <= 20; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "线程正在办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}

到这儿,线程池的作用、分类、底层代码逻辑、参数与策略的问题基本清晰,那什么时候考虑去使用线程池呢?==> 线程池适合处理耗时任务,可以充分使用目前服务器的硬件资源,加快处理速度。更确切的说是:
此时,如果不使用线程池,随意启动许多线程,容易导致系统因创建大量线程而OOM且过渡调度(过渡切换)。比如工作中遇到一个excel数据转换后批量写入库里,就可拆为一批批的小任务去insert。还有帖子说需要限制并发执行的任务数量时也可以用线程池,这儿我先想到的反而是Semaphore信号灯这个JUC辅助类。