文章目录
一、线程池是什么
,为了避免频繁地创建和销毁线程,我们用线程池维护一些线程,线程池中的线程一直处于激活状态。用户创建线程可以转换成从线程池中拿一个空闲的线程,不用的时候放回线程池。1.合理地使用线程池的好处
- 降低资源资源,通过重复利用已创建的线程降低线程和销毁造成的消耗
- 提高响应速度 当任务状态到达时,当任务到达时,任务可以不需要等到线程创建就能立即执行
- 提高线程的可管理性,线程是稀缺资源,如果可以无限制的创建,会消耗系统资源,可能造成系统的低稳定性。
二、面试中线程池原理
1.如果我们被问到线程池原理应该如何回答?
-
jdk 线程池 java.util.concurrent.ThreadPoolExecutor 构造的时候有几个重要的参数,核心线程数,最大线程数,keepAliveTime 线程空闲时间保持多久会被终止,附带了一个辅助参数,keepAlliveTime的计时单位。以及存储任务的BlockingQueue,拒绝策略 ,创建线程的工厂threadFactory也是可以自定义的。
-
当提交任务时,线程池中线程的大小比核心线程数少的时候会创建线程
-
当线程数大于或者等于核心数量小于最大线程数的时候会被添加到队列里面。但是如果队列满了会被创建线程
-
当线程等于最大线程数时,就会根据拒绝策略处理提交的任务
-
线程的数量大于核心数量的部分超过一定时间处于空闲状态,会被销毁
2.线程池的拒绝策略有哪些?
- AbortPolicy 直接抛出异常(默认拒绝策略)
- CallerRunsPolicy 在线程池没有关闭的情况下,在调用者线程直接运行提交的任务逻辑
- DiscardPolicy 直接丢弃任务
- DiscardOldestPolicy 丢弃最先提交的任务
2.1 AbortPolicy 代码:
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
根据观察可知,AbortPolicy为 java.util.concurrent.ThreadPoolExecutor 的一个静态内部类,并且实现RejectedExecutionHandler的接口. 处理逻辑为直接抛出RejectedExcecutionException异常
2.2 CallerRunsPolicy 代码:
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
直接运行调用任务的run方法
2.3 DiscardPolicy 代码:
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
啥也不做
2.4 DiscardOldestPolicy 代码:
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
把最先加入任务队列的任务移除再进行操作
提示:上面这些代码毫无难度,很容易就理解了
3.常用的线程池都有哪些?
了解了线程池的基本原理,我们看看如何使用线程池,jdk都实现了什么样的线程池。
- CachedThreadPool
可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况 - SecudleThreadPool
周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务 - SingleThreadPool
只有一条线程来执行任务,适用于有顺序的任务的应用场景 - FixedThreadPool
创建的线程池corePoolSize和maximumPoolSize值是相等的 - SingleThreadSecudlePool
只有一条线程来周期性执行任务
三、线程池的使用
1.我们先了解一下创建线程池的ThreadPoolExecutor
我们可以通过java.util.concurrent.AbstractExecutorService创建线程池,接下我们了解一下他吧
2.ThreadPoolExecutor是如何创建一个线程池的
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
- corePoolSize 线程池的基本大小(也叫线程核心数量),当线程池的线程大小小于线程池的基本大小时,提交一个任务到线程池的时候,线程池会创建一个线程来执行任务。
- workQueue 任务队列 用于保存等待执行的任务的阻塞队列
- ArrayBlockingQueue 是一个基于数组的有界阻塞队列,按照FIFO(先进先出)原则对元素进行排序
- LinkedBlockingQueue 一个基于链表结构的阻塞队列,按照FIFO(先进先出)原则对元素进行排序,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Excutors.newFixedThreadPool()
- SynchronousQueue :一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Excutors.newCachedThreadPool()使用了这个队列
- PriorityBlockingQueue 优先级阻塞队列
- maximumPoolSize 线程的最大大小,当线程池里面的线程大小小于这个数量时,如果任务队列满了,就会创建线程。否则,当用户提交任务时,线程池就不会再创建线程,而是根据饱和策略(拒绝策略)处理提交的任务。
- threadFactory 用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意思的名字。
- handler 拒绝策略也叫饱和策略
1. AbortPolicy 直接 hao抛出异常(默认拒绝策略)
2. CallerRunsPolicy 在线程池没有关闭的情况下,在调用者线程直接运行提交的任务逻辑
3. DiscardPolicy 直接丢弃任务
4. DiscardOldestPolicy 丢弃最先提交的任务 - keepAliveTime 线程池的工作线程空闲后,还能保持存活状态的时间。如果任务很多,并且每个任务很多执行的时间比较短,可以调大时间,提高线程的利用率
- TimeUnit 线程保持存活时间的单位
1. DAYS 天
2. HOURS 小时
3. MINUTES 分钟
4. MILLISESECONDS 毫秒
5. MICROSECONDS 微秒
6. NANOSECONDS 纳秒 千分之一微妙
3.向线程池提交任务
- excute()方法 参数为Runnable实例
public void execute(Runnable command)
- submit()方法 用于提交需要返回值的任务,线程池会返回一个future类型的对象
public <T> Future<T> submit(Runnable task, T result)
4.关闭线程池
可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后一个个的调用线程的interrupt方法来中断线程,所以无法响应中端线程多的任务有可能永远无法停止。
只要调用了这两个方法的任何一个,isShutDown方法返回true。当所有的任务都已经关闭以后,才表示线程池关闭成功,这时候isTerminaed方法会返回true。
- shutdown 首先将线程池的状态设置成STOP。然后尝试停止所有正在进行或暂停任务的线程,并返回等待执行任务的列表
- shutdown只是将线程池的状态变成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
任务不一定要执行完,可以调用shutdownNow
5.线程池的监控
- taskCount 线程池需要执行的任务数量
- compeletedTaskCount 线程池在运行过程中已经完成的任务数量,小于或者等于taskCount
- largestPoolSize 线程池里曾经创建过的最大线程数量。通过数据可以知道线程池是否满过
- getPoolSize 线程池的线程数量
- getActiveCount 获取活动的线程数
重写线程池的beforeExecute(),afterExecute()和terminated方法,可以在任务执行前,执行后和线程关闭前执行一些代码进行监控。比如线程的平均执行时间,最大执行时间和最小执行时间。
本次文章原本为了提高个人学习效率而写,更是为了让自己更加自律。我也非常欢迎大家一起探讨。本次文章除了自己的理解语言之外,主要借鉴《java并发编程的艺术》,jdk源码,《java高并发程序设计》等等。
本章节未完结内容:Excutor框架,如何在线程池中找堆栈,如何合理使用线程池