线程池
文章目录
一、概述
-
线程池示意图
-
使用原因
- 经常创建和销毁线程,对性能的影响很大
- 可以根据系统的需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行压力
-
思路
- 提前创建好多个线程放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建、销毁,从而实现重复利用
-
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到任务队列中,然后在创建线程后自动启动这些任务
二、参数介绍
-
通过观察 java 中
ThreadPoolExecutor
的构造函数学习线程池的七个参数public ThreadPoolExecutor ( int corePoolSize, // 核心线程数量 int maximumPoolSize, // 最大线程数(核心线程数 + 临时线程数) long keepAliveTime, // 临时线程的最大空闲时间 TimeUnit unit, // 时间单位(针对临时线程) BlockingQueue<Runnable> workQueue, // 任务队列 ThreadFactory threadFactory, // 线程工厂(可以为线程起名字),线程池中的线程默认名称为pool-m-thread-n RejectedExecutionHandler handler // 拒绝策略 ) { ... }
-
举一个生活中的例子理解上述参数
- a客户(任务)去银行(线程池)办理业务,但银行刚开始营业,窗口服务员还未就位(相当于线程池中初始线程数量为0)
- 于是经理(线程池管理者)就安排1号工作人员(创建1号线程执行任务)接待a客户
- 在a客户业务还没办完时,b客户(任务)又来了,于是经理(线程池管理者)就安排2号工作人员(创建2号线程执行任务)接待b客户(又创建了一个新的线程);假设该银行总共就2个窗口(核心线程数量是2)
- 紧接着在a,b客户都没有结束的情况下c客户来了,于是经理(线程池管理者)就安排c客户先坐到银行大厅的座位上(座位相当于任务队列)等候;并告知他:如果1、2号工作人员空出,c就可以前去办理业务
- 此时d客户又到了银行,工作人员都在忙,大厅座位也满了(核心线程无空闲,且任务队列已满)于是经理赶紧安排临时工(新创建的临时线程)在大堂站着,手持pad设备给d客户办理业务
- 假如前面的业务都没有结束的时候e客户又来了,此时正式工作人员都上了,临时工也上了,座位也满了(临时工加正式员工的总数量就是最大线程数,值为3)
- 于是经理只能按《超出银行最大接待能力处理办法》(拒绝策略)处理e客户
- 最后,进来办业务的人少了,大厅的临时工空闲时间也超过了1个小时(最大空闲时间),经理就会让这位空闲的临时工下班(销毁线程)
- 但是为了保证银行正常工作(有一个allowCoreThreadTimeout变量控制是否允许销毁核心线程,默认false),即使正式工闲着,也不得提前下班,所以1、2号工作人员继续待着(池内保留所有核心线程,默认不销毁核心线程,仅销毁达到最大空闲时间的临时线程)
-
添加一个任务的流程图
注意:
- 任务队列满了之后,才会创建非核心线程(临时线程)
- 核心线程并不是一开始就有,当提交任务时才会创建核心线程
三、ExecutorService接口的介绍
- 使用
ExecutorService
接口可以管理线程池,比如提交任务,停止任务
1. 接口中常用方法
-
void shutdown()
当任务队列中的所有任务都执行完成之后,销毁线程池 (不允许有新的任务提交)- 正在运行的任务不会受到影响,继续运行
shutdown()
之后提交的任务会抛出RejectedExecutionException
异常,代表拒绝接收
-
List<Runnable> shutdownNow()
(可能会) 停止正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表shutdownNow()
之后提交的任务会抛出RejectedExecutionException
异常,代表拒绝接收- 对于正在运行的任务
- 如果此任务的代码可以抛出
InterruptedException
异常 (如wait、sleep、join方法等),则停止此任务,抛出InterruptedException
异常 - 如果此任务的代码中无法抛出
InterruptedException
异常,则此任务会持续运行直到结束
- 如果此任务的代码可以抛出
-
<T> Future<T> submit(Callable<T> task)
提交带返回值的任务,返回一个表示该任务的 Future 对象 -
Future<?> submit(Runnable task)
提交 Runnable 任务,并返回一个表示该任务的 Future 对象 -
<T> Future<T> submit(Runnable task, T result)
提交 Runnable 任务,并返回一个表示该任务的 Future 对象注意:
Executor
接口中有execute(Runnable task)
方法,可以用来提交 Runnable 任务 -
其余方法
2. 获取接口实例
-
获取
ExecutorService
实例可以利用 JDK 中的Executors
类中的静态方法,常用的如下-
带缓存线程池
static ExecutorService newCachedThreadPool()
-
固定大小线程池
static ExecutorService newFixedThreadPool(int nThreads)
-
单线程线程池
static ExecutorService newSingleThreadExecutor()
注意:上述三者都有包含
ThreadFactory threadFactory
的参数的重载方法
-
四、带缓存线程池
-
使用方法
ExecutorService executorService = Executors.newCachedThreadPool();
-
newCachedThreadPool
方法详情public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
-
特点
- 核心线程数为0,所有线程都是临时线程,线程可重复利用
- 最大线程数为 Integer.MAX_VALUE ,表示 Java 中的最大整数,意味着线程可以无限创建
- 临时线程的最大空闲时间是60s
- 任务队列(阻塞队列)的方式为没有容量,来一个任务使用一个线程执行它,任务不会等待
- 适合任务数密集,但每个任务执行时间较短的情况
-
由于每个线程执行完之后会归还池中,在空闲时间内还可以执行其余任务,故起名为带缓存
代码实现
情况一:
public class CachedThreadPoolTest {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//提交10个任务给线程池,每个任务输出自己的索引
for (int i = 0; i < 10; i++) {
Runnable task = new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+">>"+index);
}
};
// 提交任务
cachedThreadPool.execute(task);
}
//关闭线程池
cachedThreadPool.shutdown();
}
}
运行结果:
情况二:
public class CachedThreadPoolTest {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//提交10个任务给线程池,每个任务输出自己的索引
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Runnable task = new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+">>"+index);
}
};
// 提交任务
cachedThreadPool.execute(task);
}
//关闭线程池
cachedThreadPool.shutdown();
}
}
运行结果:
五、固定大小线程池
-
使用方法
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(n);
- 参数指定 核心线程数 和 最大线程数 的大小
-
newFixedThreadPool
方法详情public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
-
特点
- 核心线程数 == 最大线程数,也就是说没有临时线程
- 无需设置临时线程的最大空闲时间
- 阻塞队列是无界的,可以存放任意数量的任务
- 适用于任务量已知,且相对耗时的任务(推荐使用)
代码实现
public class FixedThreadPoolTest {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 向线程池提交9个任务,每个任务输出对应的索引
for (int i = 0; i < 9; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(Thread.currentThread().getName()+">>"+index);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
//关闭线程池
fixedThreadPool.shutdown();
}
}
运行结果:
六、单线程线程池
-
使用方法
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
-
newSingleThreadExecutor
方法详情public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
-
特点
- 核心线程数和最大线程数都是1,没有临时线程
- 无需设置临时线程的最大空闲时间
- 阻塞队列是无界的,可以存放任意数量的任务
- 适用于希望多个任务排队执行的情况
-
与自己定义一个线程执行任务的区别
- 如果自定义的线程出现错误,则程序会终止,队列中的任务都无法执行;而线程池如果当前线程出现错误,还会新建一个线程,保证任务的正常执行
代码实现
public class SingleThreadExecutorTest {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//提交5个任务给线程池
for (int i = 0; i < 10; i++) {
final int index = i;
Runnable task = new Runnable() {
public void run() {
try {
System.out.println(Thread.currentThread().getName()+index);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//提交任务
singleThreadExecutor.execute(task);
}
//关闭线程池
singleThreadExecutor.shutdown();
}
}
运行结果:同一个线程按顺序执行5个任务
七、ScheduledExecutorService接口的介绍
ScheduledExecutorService
继承ExecutorService
接口,具备了延迟运行或定期执行任务的能力
1. 接口中常用方法
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
- 延迟时间单位是unit,延迟delay时间后执行callable
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
- 延迟时间单位是unit,延迟delay时间后执行command
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
- 延迟时间单位是unit,延迟initialDelay时间后首次执行,每间隔period时间重复执行一次command
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
- 延迟时间单位是unit,延迟initialDelay时间后首次执行,每次任务执行完成之后,再等待delay时间后执行下一次任务
2. 获取接口实例
-
获取
ScheduledExecutorService
实例可以利用 JDK 中的Executors
类中的静态方法,常用的如下static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
- 创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务
static ScheduledExecutorService newSingleThreadScheduledExecutor()
- 创建一个单线程执行程序,它允许在给定延迟后运行命令或者定期执行任务
注意:上述二者都有包含
ThreadFactory threadFactory
的参数的重载方法
八、时间调度线程池
-
使用方法
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(n);
- 参数指定核心线程数
-
newScheduledThreadPool
方法详情public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } //ScheduledThreadPoolExecutor继承了ThreadPoolExecutor public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
-
特点
- 参数指定核心线程数
- 最大线程数为 “无限大”
- 阻塞队列的方式是具有延迟特性的无界阻塞队列
- 由于
DelayedWorkQueue
队列属于无界队列,故不会产生临时线程
代码实现
情况一:schedule
方法
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
//提交10个任务至线程池
for (int i = 0; i < 10; i++) {
final int index = i;
Runnable task = new Runnable() {
public void run() {
System.out.println(new Date().toString()
+ " 第 " + (index + 1) + " 个任务延迟了 " + index + " 秒才运行");
}
};
//第一个任务延迟1秒执行,第二个任务延迟2秒执行,第三个任务延迟3秒执行...
//会立马将所有任务提交,主线程向下运行,提交之后到底什么时候执行,取决于设定值
ScheduledFuture<?> schedule = scheduledThreadPool.schedule(task, i, TimeUnit.SECONDS);
}
//ScheduledExecutorService继承ExecutorService,故可调用父接口方法
//执行完所有任务后,关闭线程池
scheduledThreadPool.shutdown();
运行结果:
情况二:scheduleAtFixedRate
方法
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//创建任务,每隔一秒执行一次,但每次任务执行时间需要三秒
Runnable task = new Runnable() {
public void run() {
try {
System.out.println(new Date().toString() + " " +
Thread.currentThread().getName() + " 任务执行了!");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
scheduledThreadPool.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);
运行结果:
出现原因:
任务的执行时间超过了任务的间隔时间,下一次任务达到了间隔时间准备执行时,发现上一次任务还没有结束,必须等待,当上一次任务执行结束之后,立即启动处于等待状态的下一次任务,所以没有按照设定的间隔时间执行任务
情况三:scheduleWithFixedDelay
方法
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//创建任务,一个任务要执行2秒,执行完成之后,再等待1秒开启下一个任务
Runnable task = new Runnable() {
public void run() {
try {
System.out.println(new Date().toString() + " " +
Thread.currentThread().getName() + " 任务执行了!");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
scheduledThreadPool.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS);
运行结果:
九、拒绝策略
-
Java 中提供了四种内置的拒绝策略
-
RejectedExecutionHandler
源码public interface RejectedExecutionHandler { void rejectedExecution(Runnable r, ThreadPoolExecutor executor); }
1. AbortPolicy
-
默认的拒绝策略
-
任务无法处理时会抛出异常
RejectedExecutionException
,不再执行这个任务 -
源码
public static class AbortPolicy implements RejectedExecutionHandler { public AbortPolicy() { } //重写的方法 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } }
2. CallerRunsPolicy
-
如果线程池没有shutdown,则调用
execute
函数的上层线程(main线程)去执行被拒绝的任务 -
源码
public static class CallerRunsPolicy implements RejectedExecutionHandler { public CallerRunsPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } }
3. DiscardOldestPolicy
-
将任务队列中最老的任务丢弃(快要被执行的任务),尝试再次提交新任务
-
源码
public static class DiscardOldestPolicy implements RejectedExecutionHandler { public DiscardOldestPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } }
4. DiscardPolicy
-
直接丢弃这个无法被处理的任务
-
源码
public static class DiscardPolicy implements RejectedExecutionHandler { public DiscardPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { //没有做处理表示直接丢弃这个任务 } }
5. 自定义拒绝策略
-
观察上述四种拒绝策略的源码,得知自定义拒绝策略有如下步骤
- 自定义策略类实现
RejectedExecutionHandler
接口 - 重写接口中的
rejectedExecution
方法
- 自定义策略类实现
-
示例
class MyPolicy implements RejectedExecutionHandler{ public MyPolicy() { } @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println(r + "被拒绝执行"); //对于无法处理的任务,仅输出了一句话,直接丢弃此任务 } }
十、自定义线程池
-
观察上述四种线程池的源码得知自定义的线程池需要
new ThreadPoolExecutor
,定义各项参数的值 -
示例
/** * 自定义线程池 * 1. 核心线程数为3,最大线程数为3 * 2. 由于没有临时线程,故不设置临时线程的空闲时间 * 3. 阻塞队列最多可以放1个任务 * 4. 拒绝策略为内置的AbortPolicy,会抛出异常 * 5. 最大同时容纳任务数为4个 * 6. 此处没有使用线程工厂 */ ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1), new ThreadPoolExecutor.AbortPolicy()); //提交8个任务,每个任务的执行时间为1秒 for(int i = 1; i < 9; i++){ int index = i; executor.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " 执行了第 " + index + " 个任务"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } //关闭线程池 executor.shutdown(); //多出了4个无法处理的任务,故一定会抛出异常
运行结果:
十一、ThreadFactory
-
当有任务提交到线程池时,会创建线程执行任务(一开始并没有核心线程)
-
可以使用
ThreadFactory
改变线程创建时的行为,比如修改线程的名字、打印日志等 -
ThreadFactory
源码public interface ThreadFactory { Thread newThread(Runnable r); }
-
使用步骤
- 创建一个类实现
ThreadFactory
接口 - 重写
newThread
方法,参数传递Runnable
实例,返回Thread
实例
- 创建一个类实现
-
示例
//定义线程工厂 class DemoThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { // 1.输出日志 System.out.println("当前时间是:" + new Date().toString()); // 2.自定义线程名字 Thread thread = new Thread(r); thread.setName("高奇泽的线程" + thread.getId()); System.out.println("新建的线程的名字是:" + thread.getName()); return thread; } } //使用线程工厂 public class ThreadPoolExecutorTest { public static void main(String[] args) { // 自定义线程池 // 使用线程工厂 ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1), new DemoThreadFactory()); //提交4个任务,每个任务的执行时间为1秒 for(int i = 0; i < 4; i++){ executor.submit(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } //关闭线程池 executor.shutdown(); } }
运行结果:
十二、线程池常用方法
1. 线程池结束状态判断
-
isShutdown()
用来判断线程池是否已经关闭- 调用
shutdownNow
和shutdown
方法,isShutdown
会返回 true(即使还有任务正在运行)
- 调用
-
isTerminated()
任务全部执行完毕,并且线程池已经关闭,才会返回 true
2. 核心线程预启动
- 默认情况下,核心线程只有在任务提交的时候才会创建
- 预启动策略,可以让核心线程提前启动,从而增强最初提交的任务运行性能
- 调用
ThreadPoolExecutor
类中的以下两个方法prestartCoreThread()
启动1个核心线程prestartAllCoreThreads()
启动所有核心线程
3. 线程和线程池切面编程
- 在线程执行前、执行后增加切面,在线程池关闭时执行某段程序
- 需要实现自己的线程池类,并覆写
beforeExecute
、afterExecute
、terminated
方法
4. 移除线程池中的任务
- 调用
ThreadPoolExecutor
类中的remove()
方法- 已经正在运行中的任务不可以删除
- execute方法提交的,未运行的任务可以删除
- submit方法提交的,未运行任务不可以删除
5. 获取线程池的各项数据
- 调用
ThreadPoolExecutor
类中的如下方法- 返回核心线程数
getCorePoolSize
- 返回当前线程池中的线程数
getPoolSize
- 返回最大允许的线程数
getMaximumPoolSize
- 返回池中同时存在的最大线程数
getLargestPoolSize
- 返回预定执行的任务总和
getTaskCount
- 返回当前线程池已经完成的任务数
getCompletedTaskCount
- 返回正在执行任务的线程的大致 数目
getActiveCount
- 返回线程池空闲时间
getKeepAliveTime
- 返回核心线程数
十三、异步计算结果Future
- 有时需要利用线程进行一些计算,然后获取这些计算的结果,可以通过
Future
对象获取线程计算的结果 Future
中的常用方法boolean cancel(boolean mayInterruptIfRunning)
试图取消对此任务的执行,参数用来指定是否中断正在运行的任务V get()
如有必要,等待计算完成,然后获取其结果(获取的是Callable接口中call方法的返回值)V get(long timeout, TimeUnit unit)
如有必要,最多等待 timeout 时间获取结果boolean isCancelled()
如果在任务正常完成前将其取消,则返回 trueboolean isDone()
如果任务已完成,则返回 true