线程池的好处
首先明白一点,线程的创建需要开辟虚拟机栈、本地方法栈、程序计数器等线程私有的内存空间。而线程销毁时需要回收这些系统资源,如果频繁地创建和销毁线程会浪费大量的系统资源,增加并发编程风险。所以,通过线程池来对线程进行统一分配、调优和监控是很有必要的。
image.png参照引用一下《阿里巴巴 Java 手册》中的一条进行说明。
在开发过程中,合理地使用线程池能够带来3个好处:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
常见四种线程池创建方法
Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:
- newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool :创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor: 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
当然,这四种常见的线程池都有其局限性,不够灵活。下面我门看看这四个方法的源码实现。
// Executors.newCachedThreadPool 源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//Executors.newFixedThreadPool 源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
....
通过查看源码,我们不难发现其实这四个常见线程池都是通过向ThreadPoolExecutor构造方法的传递不同参数生成的。下面介绍一下ThreadPoolExecutor的参数信息。
// ThreadPoolExecutor 的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- corePoolSize是线程池的核心线程数
- maximumPoolSize是线程池允许的最大线程数
- keepAliveTime为线程空闲时的存活时间
- unit是keepAliveTime的单位
- workQueue是用来保存等待被执行的线程的队列
- threadFactory,线程工厂,通常使用默认工厂,定义了线程名称生成规则。
- handler是当最大线程数和队列都满了以后,线程池的处理策略
ThreadPoolExecutor原理
提交一个任务到线程池中,线程池的处理流程如下:
- 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
- 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
-
判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
image.png
拒绝策略
一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况。ThreadPoolExecutor自带的拒绝策略如下
image.png
AbortPolicy:默认策略,直接抛出异常,throw new RejectedExecutionException
CallerRunsPolicy:如果线程池没有中止,直接用调用者的线程资源执行任务
DiscardOldestPolicy:如果线程池没有中止,移除队列第一个任务,再执行当前任务
DiscardPolicy:什么也不做,直接抛弃这个任务。
网友评论