为什么要使用线程池?
在实际的业务中,有很多场景需要用到多线程,比如,
- 数据库插入100W数据或者通过接口获取100W数据,
- 导出100W数据excel表
解决方案:
- 在1场景中,如果开启线程去处理,每个线程处理5W条,那么要开启20个线程。
- 在场景2中,因为处理时间不确定,如果采用同步的方式,最终导致链接超时。那么考虑开启线程处理。等excel生成好后,再发起请求下载。
使用以下创建线程方式
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("5万条数据入库!");
}
}).start();
问题:
由于插入数据库的时间较久,不能很快的处理,这样的话,一段时间之后,系统中就会有很多的这种插入数据的线程,最终资源耗尽。疯狂引发GC,最终服务无法对外提供服务。
虽然多线程技术可以充分发挥多核处理器的计算能力,提高生产系统的吞吐量和性能。但是,若不加控制和管理的随意使用线程,对系统的性能反而会产生不利的影响。
如果我们使用线程池的方式的话,可以合理指定线程的数量,超过数量的线程只需要排队等待线程池的线程即可,不会出现线程池过多而消耗系统资源的情况。
什么是线程池?
本质上只是对线程进行管理的逻辑。
使用线程池的好处
- 降低资源消耗。(免去频繁创建线程资源占用)
- 提高响应速度。(免去频繁创建线程时间)
- 对线程进行管理。
JDK线程池实现
ExecutorService 接口如下:
Executors
其中常用几类如下:
public static ExecutorService newFixedThreadPool()
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newScheduledThreadPool()
- newFixedThreadPool:该方法返回一个固定线程数量的线程池;
- newSingleThreadExecutor:该方法返回一个只有一个现成的线程池;
- newCachedThreadPool:返回一个可以根据实际情况调整线程数量的线程池;
- newSingleThreadScheduledExecutor:该方法和newSingleThreadExecutor的区别是给定了时间执行某任务的功能,可以进行定时执行等;
- newScheduledThreadPool:在4的基础上可以指定线程数量。
查看: newFixedThreadPool
查看: newCachedThreadPoo
结论: 类似的其他方法一样,在Executors内部创建线程池的时候,实际创建的都是一个ThreadPoolExecutor对象,只是对ThreadPoolExecutor构造方法,进行了默认值的设定。
ThreadPoolExecutor的构造方法如下:
参数说明:
- corePoolSize 保留的最小线程数,他们不会消失,除非设置了allowCoreThreadTimeOut。
- maximumPoolSize 池中允许存在的最大线程。
- keepAliveTime 除了核心线程之外的多余线程,空闲超过这个时间就会被回收。
- unit keepAliveTime的时间单位
- workQueue 线程队列实现类
- threadFactory 线程工厂实现类
- handler 当线程被阻塞时的处理方法
以下条件触发异常:IllegalArgumentException
corePoolSize < 0
keepAliveTime < 0
maximumPoolSize <= 0
maximumPoolSize < corePoolSize
workQueue 常用三个实现类
- ArrayBlockingQueue (有界队列,可迭代)
数组队列:一旦创建,容量不能改变,有序,按照加入顺序执行,当队列满了,线程将被阻塞 - LinkedBlockingQueue (无界队列,可迭代)
链式队列:比基于数组的队列具有更高的吞吐量,可预测性较差,在每次插入时动态创建,会导致排队超过内存容量,除非指定大小 - SynchronousQueue (同步队列)
队列中只能有一个任务在执行,其他任务将阻塞。
如何创建?
new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1),Executors.defaultThreadFactory());
异常处理
默认 RejectedExecutionHandler 实现 是AbortPolicy 类 ,该策略会抛出异常,并丢弃当前任务。
在代码中catch 即可。