前言:当我们线程创建过多时,容易引发内存溢出,这时我们如何解决这个问题呢?
一、什么是线程池
顾名思义,线程池就是一个用来装线程的池子(容器)。首先需要创建若干个可执行的线程放入一个池中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。
二、线程池原理详解
首先需要创建一个线程池,然后需要创建若干个可执行的线程放入一个池中(Thread1-5),但是只开启一部分默认的核心线程(例如Thread1-3),接下来有任务进入并且假设都长时间持续占有线程时,当只有几个(这里如果是小于等于三个)任务时,会按顺序分配到三个核心线程中,如果大于三个则会进入到任务队列(workQueue,Task4-7)中等待,如果任务超过了workQueue的最大容纳量,则创建新的线程(Thread4-5),并将workQueue中的任务按顺序分别到线程中,Task8-9进入workQueue,如果还有Task加入,那么调用拒绝策略方法,防止Task进入;如果线程Thread4-5执行完任务后在一定时间内没有任务进入,则会被回收。
三、线程池的优点
因此,从上面的分析来看,对比不使用线程池的情况(即创建线程对象、执行任务、释放线程对象),线程池具有以下优点:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。(前面的Task先执行完后后面的Task可以复用前面的线程)
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。(提前创建好核心线程Thread1-3)
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
四、线程池的参数含义
首先,我们来看源码:
Executor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory
);
public ThreadPoolExecutor (int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable workQueue>,
ThreadFactory threadFactory )
- corePoolSize:核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
- maximumPoolSize:线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
- keepAliveTime:线程闲置超时时长。如果超过该时长,非核心线程就会被回收。
- unit:指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
- workQueue:任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
- threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
- handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
五、线程池的使用流程
// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory);
// 向线程池提交任务
threadPool.execute(new Runnable() {
@Override
public void run() {
... // 线程执行的任务
}
});
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
六、常见线程池
- 定长线程池(FixedThreadPool):只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
- 定时线程池(ScheduledThreadPool):核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
- 可缓存线程池(CachedThreadPool):无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
- 单线程化线程池(SingleThreadExecutor):只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
七、拒绝策略
- AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
- CallerRunsPolicy:由调用线程处理该任务。
- DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
- DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。