1、为什么要使用线程池?
在Java 并发编程中,线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的应用程序都可以使用线程池。在开发过程中,合理地使用线程池能够带来以下几个好处。
- 降低资源消耗。通过重复利用已创建的线程降低线程的创建和销毁造成的资源消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行,从而提高应用系统的响应速度。
- 提高线程的可管理性。线程属于稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以将线程进行统一分配、调优和 监控等。
2、线程池核心类的类关系
线程池中核心的类和接口大致有以下几个,它们分别是:
- Executor:它是一个接口,是 Executor 框架的基础,将任务的提交与任务的执行分离开来。
- ExecutorService: 它是一个接口,继承了 Executor,在其上做了一些扩展,如 shutdown()、submit() 等,可以说是真正的线程池接口。
- AbstractExecutorService:它是一个抽象类,实现了 ExecutorService 接口中的大部分方法。
- ThreadPoolExecutor: 它线程池的核心实现类,用来执行被提交的任务。
- ScheduledExecutorService:它是一个接口,继承了 ExecutorService 接口,提供了带"周期执行"功能的 ExecutorService。
- ScheduledThreadPoolExecutor:它是一个实现类,实现 ScheduledExecutorService 接口,可以在给定的延迟后执行任务, 或者定期执行任务。它比 Timer 更灵活,功能更强大。
它们之间类的UML图如下所示:
3、线程池的生命周期状态
线程池的生命周期状态,一共5种,线程池中使用一个 AtomicInteger 的 ctl 变量表示线程池的状态和数量,高3位表示线程池状态,低29位表示线程数量。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
- RUNNING: 接受新任务并处理排队的任务,一旦线程被创建初始状态就是 RUNNING。
- SHUTDOWN: 不接受新任务,但处理排队的任务和正在执行的任务。
- STOP: 不接受新任务,不处理排队的任务,并中断正在进行的任务。
- TIDYING: 所有任务都已终止,workerCount(线程数)为零,线程状态过渡到 TIDYING 状态将要执行终止方法 terminated()。
- TERMINATED: 终止方法 terminated() 执行完成
各状态之间转换如下所示:
- RUNNING -> SHUTDOWN :调用 shutdown() 方法,可能隐含在finalize()中。
- (RUNNING or SHUTDOWN) -> STOP :调用 shutdownNow() 方法
- SHUTDOWN -> TIDYING:当队列和线程池都为空时2111
- STOP -> TIDYING: 当线程池为空时
- TIDYING -> TERMINATED:当 terminated() 方法执行完成
使用一个变量 ctl 表示线程池的两个变量值,高3位表示线程池状态,低29位表示线程数量。
为什么要这样设计,不用两个变量,使用一个变量来表示呢?一个变量可以使用一条CAS指令,可以无锁,保证原子性。如果是两个变量的话,就要使用两条CAS指令,不能保证原子性。
4、线程池的工作原理
1)线程池中一开始是不存在线程的,当一个任务被提交给线程池后,线程池会创建一个新线程来执行任务。如果当前线程池中运行的线程数少于corePoolSize,则创建新线程来执行任务。
2)如果线程池中运行的线程数达到核心线程数corePoolSize的上限,则将新提交的任务加入BlockingQueue队列当中去。
3)如果无法将新提交的任务加入BlockingQueue队列中(有界队列,队列已满),则创建新的线程来执行任务,前提是maximumPoolSize大于corePoolSize。
4)如果线程池中线程数达到maximumPoolSize,任务将被拒绝,执行拒绝策略,并调用RejectedExecutionHandler接口的rejectedExecution()方法。
特别注意:如果线程池的核心线程数被设置为0,那么提交的任务会启用空闲线程来执行。
线程池的工作原理图如下所示:
5、ThreadPoolExecutor 类的构造方法
ThreadPoolExecutor 类一共有 4 个构造方法,如下图所示:
接下来分析一下参数最多的一个构造方法,其他的几个构造方法不再过多讲述。
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;
}
此方法一共有7个参数:
1)corePoolSize: 核心线程数
2)maximumPoolSize: 最大线程数
3)keepAliveTime: maximumPoolSize-corePoolSize 线程数(空闲线程)的存活时间,如果超时,则把线程销毁。
4)unit:参数 keepAliveTime 的时间单位
5)workQueue:任务存放的队列
6)threadFactory:线程工程,主要是给线程取一个自定义的名字
7)handler:拒绝策略
6、如何合理地配置线程池
线程池的大小决定着系统的性能,数量过大或者过小线程池都没有办法发挥最优的系统性能。那如何合理地配置线程池?想必这是每个开发人员必须要了解的知识点,想要合理地配置线程池,就必须首先分析任务特性,可以从以下几个方面来分析。
- 任务性质:CPU密集型、IO密集型和混合型。
- 任务优先级:高、中、低。
- 任务执行时间:长、中、短。
- 任务依赖性:是否依赖其他系统资源,比如数据库连接等等。
定义下文中的几个变量:
- NCPU:表示处理器(CPU)的核心数目,Java可以通过Runtime.getRuntime().availableProcessors()方法获取。
- UCPU:表示期望CPU 的利用率(介于 0 和 1 之间)
- W/C:表示等待时间与计算时间的比率。等待时间与计算时间在 Linux 下使用相关的 vmstat 命令或者 top 命令查看。
CPU密集型任务:
尽量使用较小的线程池数量,一般为 NCPU+1。 因为CPU密集型任务使得CPU使用率很高,若设置过大的线程数,会造成CPU过度切换,反而会降低系统的性能。
OI密集型任务:
可以使用稍微大的线程池数量,一般为 NCPU*2 。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。在进行I/O操作的时候,是将任务交给DMA(Direct Memory Access)来处理,请求发出后CPU就不管了,在DMA处理完后通过中断通知CPU处理完成了,I/O操作消耗的CPU时间很少。
对于IO密集型任务的最佳线程数,有个最佳计算公式:线程数 = NCPU * UCPU * (1 + W/C)。
混合型任务:
可以将任务拆分成IO密集型任务和CPU密集型任务,使用不同的线程池去执行提交的任务。确保拆分完成后两个任务的执行时间没有明显的差距,那么这样的拆分可以给系统带来效率的提升;如果有明显的差距,则没有必要拆分。
优先级不同的任务可以使用优先级队列 PriorityBlockingQueue 来处理。它可 以让优先级高的任务先执行。
执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先 级队列,让执行时间短的任务先执行。
依赖数据库连接池的任务,因为线程提交 SQL 后需要等待数据库返回结果, 等待的时间越长,则 CPU 空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用 CPU。
7、示例分析
7.1 深入解析线程池各参数的基本应用
第一、核心线程和空闲线程(最大线程数 - 核心线程数)的基本应用
定义核心线程数为1,最大线程数为2,队列长度为2,主线程提交4个任务。
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 核心线程和空闲线程(最大线程数 - 核心线程数)的基本应用
* <p>
* 定义:核心线程数为1,最大线程数为2,队列长度为2,主线程提交4个任务。
* 1、task1被线程t1执行
* 2、task4被线程t2执行
* 3、task2、task3放在阻塞队列中,等待线程t1或者t2执行完再去队列中取排队的任务。
* 空闲线程和核心线程都会从队列当中随机去获取任务,前提是空闲线程被启用。
*/
public class UseThreadPoolExecutor1 {
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static AtomicInteger i = new AtomicInteger();
private static ExecutorService executorService = new ThreadPoolExecutor(1,
2,
1,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), (r) -> {
// 给线程取个名字
return new Thread(r, " t" + i.incrementAndGet());
}, new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
for (int j = 1; j <= 4; j++) {
int finalJ = j;
executorService.execute(() -> {
System.out.println(getSystemTime() + " 线程名称:" + Thread.currentThread().getName() + " 正在执行 task" + finalJ);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程名称:" + Thread.currentThread().getName() + " task" + finalJ + " 执行完毕。。。。。。 ");
});
}
}
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
}
执行结果如下:
结果分析:
task1被线程t1执行;task4被线程t2执行;task2、task3放在阻塞队列中,等待线程t1或者t2执行完再去队列中取排队的任务。空闲线程和核心线程都会从队列当中随机去获取任务,前提是空闲线程被启用。
第二、空闲线程(最大线程数 - 核心线程数)超时的基本应用,存活的线程。
定义核心线程数为1,最大线程数为2,队列长度为1,主线程提交3个任务。
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 空闲线程(最大线程数 - 核心线程数)超时的基本应用,存活的线程。
* <p>
* 定义:核心线程数为1,最大线程数为2,队列长度为1,主线程提交3个任务。
* 1、task1被线程t1执行
* 2、task3被线程t2执行
* 3、task2放在阻塞队列中,等待线程t1或者t2执行完再去队列中取排队的任务。
* 空闲线程和核心线程都会从队列当中随机去获取任务,前提是空闲线程被启用。
* 当核心线程抢到了队列中的任务task2执行;空闲线程一直处于空闲状态,timeout 结束,空闲线程被销毁。
*/
public class UseThreadPoolExecutor2 {
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static AtomicInteger i = new AtomicInteger();
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
2,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), (r) -> {
// 给线程取个名字
return new Thread(r, " t" + i.incrementAndGet());
}, new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
for (int j = 1; j <= 3; j++) {
int finalJ = j;
threadPoolExecutor.execute(() -> {
System.out.println(getSystemTime() + " 当前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " 正在执行 task" + finalJ);
try {
// 当核心线程抢到了队列中的任务task2执行;空闲线程一直处于空闲状态,timeout 结束,空闲线程被销毁。
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 业务执行结束,目前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " task" + finalJ + " 执行完毕。。。。。。 ");
});
}
}
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
}
执行结果如下:
结果分析:
task1被线程t1执行;task3被线程t2执行;task2放在阻塞队列中,等待线程t1或者t2执行完再去队列中取排队的任务。空闲线程和核心线程都会从队列当中随机去获取任务,前提是空闲线程被启用。当核心线程抢到了队列中的任务task2执行;空闲线程一直处于空闲状态,timeout 结束,空闲线程被销毁,此时线程池中的线程数量为1。
7.2 拒绝策略
线程池的拒绝策略,当线程池的阻塞队列满了且没有空闲的工作线程时,如果继续往线程池中提交任务,那么就需要采取一种策略来处理提交的任务,线程池给我们提供了4种策略,方便调用者来选择合适的拒绝策略,它们分别是:
1)AbortPolicy:直接抛出异常,默认策略;执行此策略,线程池不能被shutdown。
2)CallerRunsPolicy:用调用者所在的线程来执行提交的任务;
3)DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4)DiscardPolicy:直接丢弃任务;
我们也可以根据实际开发中的应用场景实现自定义拒绝策略,实现接口 RejectedExecutionHandler 即可,比如记录日志或持久化存储不能处理的任务等等。
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 执行拒绝策略:
* 线程池的拒绝策略,当线程池的阻塞队列满了且没有空闲的工作线程时,如果继续往线程池中提交任务,那么就需要采取一种策略来处理提交的任务,
* 线程池给我们提供了4种策略,方便调用者来选择合适的拒绝策略,它们分别是:
* 1、AbortPolicy:直接抛出异常,默认策略;执行此策略,线程池不能被shutdown。
* 2、CallerRunsPolicy:用调用者所在的线程来执行提交的任务;
* 3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
* 4、DiscardPolicy:直接丢弃提交的任务;
* 我们也可以根据实际开发中的应用场景实现自定义拒绝策略,实现接口 RejectedExecutionHandler 即可,比如记录日志或持久化存储不能处理的任务等等。
* <p>
* 定义:核心线程数为1,最大线程数为2,队列长度为1,主线程提交4个任务。
* 1、task1被线程t1执行
* 2、task3被线程t2执行
* 3、task2放在阻塞队列中,等待线程t1或者t2执行完再去队列中取排队的任务。
* 空闲线程和核心线程都会从队列当中随机去获取任务,前提是空闲线程被启用。
* 4、主线程提交任务task4,由于队列已满,线程数达到最大数且还在执行其他任务,因此线程池将执行拒绝策略。
*
*/
public class UseThreadPoolExecutor3 {
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static AtomicInteger i = new AtomicInteger();
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
/**
* 拒绝策略:AbortPolicy
* 如果执行此拒绝策略,直接抛出异常,且线程池不能被shutdown。
*/
private static void m1(){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
2,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), (r) -> {
// 给线程取个名字
return new Thread(r, " t" + i.incrementAndGet());
}, new ThreadPoolExecutor.AbortPolicy());
for (int j = 1; j <= 4; j++) {
int finalJ = j;
try {
Thread.currentThread().sleep(500);
System.out.println(getSystemTime() + " main 线程准备提交任务:task" + finalJ);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPoolExecutor.execute(() -> {
System.out.println(getSystemTime() + " 当前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " 正在执行 task" + finalJ);
try {
Thread.currentThread().sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 业务执行结束,目前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " task" + finalJ + " 执行完毕。。。。。。 ");
});
}
threadPoolExecutor.shutdownNow();
System.out.println(getSystemTime() + "线程名称:" + Thread.currentThread().getName() + " 关闭线程池......");
}
/**
* 拒绝策略:CallerRunsPolicy
* 如果执行此拒绝策略,用调用者所在的线程来执行提交的任务;
*/
private static void m2(){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
2,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), (r) -> {
// 给线程取个名字
return new Thread(r, " t" + i.incrementAndGet());
}, new ThreadPoolExecutor.CallerRunsPolicy());
for (int j = 1; j <= 4; j++) {
int finalJ = j;
try {
Thread.currentThread().sleep(500);
System.out.println(getSystemTime() + " main 线程准备提交任务:task" + finalJ);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPoolExecutor.execute(() -> {
System.out.println(getSystemTime() + " 当前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " 正在执行 task" + finalJ);
try {
Thread.currentThread().sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 业务执行结束,目前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " task" + finalJ + " 执行完毕。。。。。。 ");
});
}
threadPoolExecutor.shutdown();
System.out.println(getSystemTime() + "线程名称:" + Thread.currentThread().getName() + " 关闭线程池......");
}
/**
* 拒绝策略:DiscardOldestPolicy
* 如果执行此拒绝策略,丢弃阻塞队列中靠最前的任务,并执行当前任务;
*/
private static void m3(){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
2,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), (r) -> {
// 给线程取个名字
return new Thread(r, " t" + i.incrementAndGet());
}, new ThreadPoolExecutor.DiscardOldestPolicy());
for (int j = 1; j <= 4; j++) {
int finalJ = j;
try {
Thread.currentThread().sleep(500);
System.out.println(getSystemTime() + " main 线程准备提交任务:task" + finalJ);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPoolExecutor.execute(() -> {
System.out.println(getSystemTime() + " 当前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " 正在执行 task" + finalJ);
try {
Thread.currentThread().sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 业务执行结束,目前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " task" + finalJ + " 执行完毕。。。。。。 ");
});
}
threadPoolExecutor.shutdown();
System.out.println(getSystemTime() + "线程名称:" + Thread.currentThread().getName() + " 关闭线程池......");
}
/**
* 拒绝策略:使用 DiscardPolicy
* 如果执行此拒绝策略,直接丢弃提交的任务;
*/
private static void m4(){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
2,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), (r) -> {
// 给线程取个名字
return new Thread(r, " t" + i.incrementAndGet());
}, new ThreadPoolExecutor.DiscardPolicy());
for (int j = 1; j <= 4; j++) {
int finalJ = j;
try {
Thread.currentThread().sleep(500);
System.out.println(getSystemTime() + " main 线程准备提交任务:task" + finalJ);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPoolExecutor.execute(() -> {
System.out.println(getSystemTime() + " 当前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " 正在执行 task" + finalJ);
try {
Thread.currentThread().sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 业务执行结束,目前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " task" + finalJ + " 执行完毕。。。。。。 ");
});
}
threadPoolExecutor.shutdown();
System.out.println(getSystemTime() + "线程名称:" + Thread.currentThread().getName() + " 关闭线程池......");
}
public static void main(String[] args) {
m1();
//m2();
//m3();
//m4();
}
}
拒绝策略:AbortPolicy,方法m1()执行结果如下:
结果分析:
从执行结果来看,如果使用 AbortPolicy 作为线程池的拒绝策略,当队列已满,线程数达到最大数且还在执行其他任务,如果主线程再往线程池中提交任务(task4),线程池将执行拒绝策略,直接抛出异常 RejectedExecutionException。后续线程池调用shutdown也不会被执行,因此线程池也不会被关闭。
拒绝策略:CallerRunsPolicy,方法m2()执行结果如下:
结果分析:
从执行结果来看,如果使用 CallerRunsPolicy 作为线程池的拒绝策略,当队列已满,线程数达到最大数且还在执行其他任务,如果主线程再往线程池中提交任务(task4),线程池将执行拒绝策略,任务(task4)将被调用者所在的线程来执行,后续线程池调用shutdown会被执行关闭。
拒绝策略:DiscardOldestPolicy,方法m3()执行结果如下:
结果分析:
从执行结果来看,如果使用 DiscardOldestPolicy 作为线程池的拒绝策略,当队列已满,线程数达到最大数且还在执行其他任务,如果主线程再往线程池中提交任务(task4),线程池将执行拒绝策略,丢弃阻塞队列中靠最前的任务(task2),并执行当前任务(task4),后续线程池调用shutdown会被执行关闭。
拒绝策略:DiscardPolicy,方法m4()执行结果如下:
结果分析:
从执行结果来看,如果使用 DiscardPolicy 作为线程池的拒绝策略,当队列已满,线程数达到最大数且还在执行其他任务,如果主线程再往线程池中提交任务(task4),线程池将执行拒绝策略,直接丢弃提交的任务(task4),后续线程池调用shutdown会被执行关闭。
7.3 线程池的扩展
自定义一个扩展类继承ThreadPoolExecutor类,重写父类的beforeExecute方法和afterExecute方法,可以实现在每个任务执行前后做一些操作,相当于执行了一个切面。 重写terminated方法,可以实现在调用shutdown后执行terminated方法。
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池的扩展
* <p>
* 自定义一个扩展类继承ThreadPoolExecutor类,重写父类的beforeExecute方法和afterExecute方法,
* 可以实现在每个任务执行前后做一些操作,相当于执行了一个切面。
* 重写terminated方法,可以实现在调用shutdown后执行terminated方法。
*/
public class UseThreadPoolExecutorExt4 extends ThreadPoolExecutor {
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static UseThreadPoolExecutorExt4 useThreadPoolExecutor4 = new UseThreadPoolExecutorExt4(1, 1, 0, TimeUnit.NANOSECONDS, new ArrayBlockingQueue<>(2));
public UseThreadPoolExecutorExt4(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行前置业务");
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行后置业务");
}
@Override
protected void terminated() {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " shutdown 后执行");
}
public static void main(String[] args) {
useThreadPoolExecutor4.execute(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行业务操作。。。");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
useThreadPoolExecutor4.shutdown(); // 调用shutdown后执行terminated方法
}
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
}
执行结果如下:
结果分析系:
从执行结果来看,可以看到,每个任务执行前后都会调用 beforeExecute 和afterExecute 方法,相当于执行了一个切面。而在调用 shutdown 方法后则会调用 terminated 方法。
7.4 提交任务
execute() 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
submit() 方法用于提交需要返回值的任务。线程池会返回一个future 类型的对象,通过这个 future 对象可以判断任务是否执行成功,并且可以通过 future 的 get() 方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完,抛出 TimeoutException 异常。
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 线程池的提交任务
*/
public class UseThreadPoolExecutor5 {
private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static AtomicInteger i = new AtomicInteger();
private static ExecutorService executorService = new ThreadPoolExecutor(1,
2,
1,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), (r) -> {
// 给线程取个名字
return new Thread(r, " t" + i.incrementAndGet());
}, new ThreadPoolExecutor.AbortPolicy());
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
/**
* 提交一个任务,任务不需要有返回值
*/
private static void m1() {
executorService.execute(() -> {
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + "执行任务");
});
}
/**
* 提交需要有返回值的任务
* 调用 future 的 get 方法获取返回值
* 1、调用无参数的get方法,主线程会等待任务执行完返回
* 2、调用有参数的get方法,超时获取任务返回值,如在设定的时间范围内未获取数据,则将超时抛异常
*/
private static void m2() throws ExecutionException, InterruptedException {
Future<String> future = executorService.submit(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 正在执行任务");
TimeUnit.SECONDS.sleep(2);
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行完任务,准备返回!!!");
return "SUCCESS";
});
// 调用get方法会阻塞,直到获取任务的返回值
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " 获取返回值,result=" + future.get());
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " end ...");
}
/**
* 提交需要有返回值的任务
* 调用 future 的 get 方法获取返回值
* 1、调用无参数的get方法,主线程会等待任务执行完返回
* 2、调用有参数的get方法,超时获取任务返回值,如在设定的时间范围内未获取数据,则将超时抛异常
*/
private static void m3() throws ExecutionException, InterruptedException, TimeoutException {
Future<String> future = executorService.submit(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 正在执行任务");
TimeUnit.SECONDS.sleep(2);
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行完任务,准备返回!!!");
return "SUCCESS";
});
// 调用get方法超时获取返回值,如在设定的时间范围内未获取数据,则将超时抛异常
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " 超时获取返回值,result=" + future.get(1, TimeUnit.SECONDS));
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " end ...");
}
/**
* 批量执行任务
* 执行给定的任务,当所有任务都执行完成后,返回其执行结果和状态列表
*
* @throws InterruptedException
*/
private static void m4() throws InterruptedException {
ExecutorService executorServiceAll = new ThreadPoolExecutor(3,
3,
1,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), (r) -> {
// 给线程取个名字
return new Thread(r, " t" + i.incrementAndGet());
}, new ThreadPoolExecutor.AbortPolicy());
List<Future<String>> futures = executorServiceAll.invokeAll(Arrays.asList(
() -> {
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " 执行任务 1");
return "执行结果1";
}, () -> {
TimeUnit.SECONDS.sleep(3);
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " 执行任务 2");
return "执行结果2";
}, () -> {
System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " 执行任务 3");
return "执行结果3";
}
));
System.out.println(getSystemTime() + " main start...");
futures.forEach(f -> {
try {
System.out.println(getSystemTime() + " 获取返回结果,result=" + f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
System.out.println(getSystemTime() + " main end...");
}
/**
* 执行批量任务:返回最先执行完成任务的结果
* 执行给定的任务,返回一个已成功完成的任务的结果(例如,没有抛出异常),
* 如果有的话,在正常或异常返回时,未完成的任务将被取消。
* 如果在执行此操作时修改了给定的集合,则此方法的结果不明确。
*
* @throws ExecutionException
* @throws InterruptedException
*/
private static void m5() throws ExecutionException, InterruptedException {
ExecutorService executorServiceAny = new ThreadPoolExecutor(4,
4,
1,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), (r) -> {
// 给线程取个名字
return new Thread(r, " t" + i.incrementAndGet());
}, new ThreadPoolExecutor.AbortPolicy());
String value = executorServiceAny.invokeAny(Arrays.asList(
() -> {
System.out.println(getSystemTime() + " 执行任务1 begin ...");
TimeUnit.MILLISECONDS.sleep(3000);
System.out.println(getSystemTime() + " 执行任务1 end ...");
return "执行结果1";
}, () -> {
System.out.println(getSystemTime() + " 执行任务2 begin ...");
TimeUnit.MILLISECONDS.sleep(500);
System.out.println(getSystemTime() + " 执行任务2 end ...");
return "执行结果2";
}, () -> {
System.out.println(getSystemTime() + " 执行任务3 begin ...");
TimeUnit.MILLISECONDS.sleep(2000);
System.out.println(getSystemTime() + " 执行任务3 end ...");
return "执行结果3";
}, () -> {
System.out.println(getSystemTime() + " 执行任务4 begin ...");
TimeUnit.MILLISECONDS.sleep(2000);
System.out.println(getSystemTime() + " 执行任务4 end ...");
return "执行结果4";
}
));
System.out.println(getSystemTime() + " main start ......");
System.out.println(getSystemTime() + " 执行结果,result=" + value);
System.out.println(getSystemTime() + " main end ......");
}
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
//m1();
//m2();
//m3();
//m4();
m5();
}
}
方法m1()执行结果:
提交一个任务,任务不需要有返回值,简单得不能再简单了,调用线程池的 execute() 方法即可。
方法m2()执行结果:
提交一个任务,任务需要有返回值,调用 future 对象的无参数的 get() 方法,主线程会等待任务执行完返回后再执行其他操作。
方法m3()执行结果:
提交一个任务,任务需要有返回值,调用 future 对象有参数的 get() 方法,超时获取任务返回值,如在设定的时间范围内未获取数据,则将超时抛异常 TimeoutException。
方法m4()执行结果:
批量执行任务,执行给定的任务,当所有任务都执行完成后,返回其执行结果和状态列表。
方法m5()执行结果:
执行批量任务,返回最先执行完成任务的结果,未执行完成的任务将会放弃执行。
7.5 关闭线程池
关闭线程池,可以通过调用线程池的 shutdown() 或 shutdownNow()方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt() 方法来中断线程,所以无法响应中断的任务可能永远无法终止。两个关闭线程池的方法存在一定的区别,shutdownNow() 首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而 shutdown() 只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。
线程池只要调用了上面介绍的这两个关闭方法中的任意一个,再调用 isShutdown() 方法会返回true。当所有的任务都已经执行完后,才表示线程池关闭成功,这时调用 isTerminated() 方法会返回true。至于应该使用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown() 方法来关闭线程池,如果任务可以不用执行完,则可以调用 shutdownNow() 方法来关闭线程池。
线程池调用 shutdown() 方法后,调用线程并不会等待所有任务都执行结束,而是继续执行其他业务操作。如果想要等待线程池的所有任务都执行结束,调用线程再去执行其他操作的话,可以调用此方法来超时等待线程池执行任务 boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
package com.enjoy.lspj.threadpool;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 关闭线程池,一共有两种方式
* 1、调用 shutdown() 方法
* 2、调用 shutdownNow() 方法
*/
public class UseThreadPoolExecutor6 {
private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static AtomicInteger i = new AtomicInteger();
private static ExecutorService executorService = new ThreadPoolExecutor(
2,
2,
1,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
(r) -> {
// 给线程取个名字
return new Thread(r, " t" + i.incrementAndGet());
},
new ThreadPoolExecutor.AbortPolicy());
private static String getSystemTime() {
return sdf.format(System.currentTimeMillis());
}
/**
* 调用 shutdown() 方法关闭线程池
* 最多2个线程,提交4个任务,其中1个任务会在队列当中,1个任务不会被正常提交
* 备注:调用了shutdown() 方法,线程池状态变为 SHUTDOWN,不在接受新的任务,但是线程池还是会执行正在执行的任务和队列中的任务
*
* @throws InterruptedException
*/
private static void shutdown1() throws InterruptedException {
// t1 执行 task1
executorService.execute(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task1--start----");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task1--end----");
});
// t2 执行 task2
executorService.execute(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task2--start----");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task2--END----");
});
// t1 || t2 正在执行任务 , task3 放入队列
executorService.execute(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task3--start----");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task3--END----");
});
// 执行shutdown
System.out.println(getSystemTime() + " shutdown");
executorService.shutdown();
// 由于调用了线程池的 shutdown() 方法,线程池不在接受新任务
executorService.execute(() -> {
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task4--start----");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task4--END----");
});
}
/**
* 调用 shutdown后,调用线程并不会等待所有任务运行结束,可以调用此方法来超时等待
* boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
* 在线程池状态终结之后做一些其他工作
* @throws InterruptedException
*/
private static void shutdown2() throws InterruptedException {
//t1
executorService.execute(()->{
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task1--start----");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task1--END----");
});
//t2
executorService.execute(()->{
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task2--start----");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task2--END----");
});
// t1 || t2
executorService.execute(()->{
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task3--start----");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task3--END----");
});
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 shutdown");
executorService.shutdown();
//等待线程池成为终结状态执行,超时等待,为了线程池终结之后做一些其他工作
executorService.awaitTermination(13,TimeUnit.SECONDS);
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 main end");
}
/**
* 调用 shutdown() 方法关闭线程池
* 最多2个线程,提交4个任务,其中1个任务会在队列当中,1个任务不会被正常提交
* 备注:调用了shutdownNow() 方法,线程池状态变为 STOP,不在接受新的任务,不处理排队的任务,会将队列中的任务返回,并用 interrupt 的方式中断正在执行的任务
*
* @throws InterruptedException
*/
private static void shutdownNow() {
//t1 执行 task1
executorService.execute(()->{
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task1--start----");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task1--END----");
});
//t2 执行 task1
executorService.execute(()->{
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task2--start----");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task2--END----");
});
// t1 || t2 正在执行任务 , task3 放入队列
executorService.execute(()->{
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task3--start----");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task3--END----");
});
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 shutdownNow");
//返回的是队列当中排队的任务
List<Runnable> runnables = executorService.shutdownNow();
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 queue size: " + runnables.size());
// task4 线程池调用 shutdownNow() 方法后,线程池状态为 STOP ,不再接受新任务
executorService.execute(()->{
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task4--start----");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task4--END----");
});
}
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
shutdown1();
//shutdown2();
//shutdownNow();
}
}
shutdown1()方法,执行结果如下:
从执行结果来看,当调用线程池的 shutdown() 方法后,线程池不再接收新的任务(task4),但是线程池还是会执行正在执行的任务和队列中的任务。
shutdown2()方法,执行结果如下:
从执行结果来看,当调用 shutdown() 方法后,调用线程并不会等待所有任务运行结束,而是继续执行自己的业务操作,可以调用此方法来超时等待 awaitTermination(long timeout, TimeUnit unit) 在线程池状态终结之后做一些其他工作。
shutdownNow()方法,执行结果如下:
从执行结果来看,当调用 shutdownNow() 方法后,线程池不在接收新的任务,不处理排队的任务,会将队列中的任务返回,并用 interrupt 的方式中断正在执行的任务。
线程池的工作原理及其基本应用就分析到这里啦,作者能力有限,有分析不到位的地方欢迎各位读者留言讨论,下一篇将详细讲解使用 Executors 工厂创建线程池的5种方法。
备注:博主微信公众号,不定期更新文章,欢迎扫码关注。