目录
1. 线程池原理
Java线程需要JVM和操作系统配合完成大量的工作
- 必须为线程堆栈分配和初始化大量内存块,其中包含至少1M的栈内存
- 需要进行系统调用,以便在操作系统中创建和注册本地线程
频繁创建和销毁线程非常低效,通过使用线程池可以提升性能,方便线程的管理
2. JUC的线程池架构
JUC就是java.util.concurrent工具包的简称,该工具包是从 JDK 1.5 开始加入到 JDK,用于完成高并发、处理多线程的一个工具包。
JUC中的线程池如图所示:
- Executor
它是Java异步目标任务的“执行者”接口,其目的是来执行目标线程void execute(Runnable command)
- ExecutorService
ExecutorService 提供了“接收异步任务、并转交给执行者”的方法,如submit 系列方法、 invoke 系列方法等等。
//向线程池提交单个异步任务
Future submit(Callable task);
//向线程池提交批量异步任务
List<Future> invokeAll(Collection<? extends Callable> tasks) throws InterruptedException;
- AbstractExecutorService
AbstractExecutorService 存在的目的是为 ExecutorService 中的接口提供了默认实现。 - ThreadPoolExecutor
ThreadPoolExecutor 就是“线程池”实现类。ThreadPoolExecutor 是 JUC 线程池的核心实现类。线程的创建和终止需要很大的开销,线程池中预先提供了指定数量的可重用线程,所以使用线程池会节省系统资源,并且每个线程池都维护了一些基础的数据统计,方便线程的管理和监控。 - ScheduledThreadPoolExecutor
它提供了 ScheduledExecutorService线程池接口中“延时执行”和“周期执行”等抽象调度方法的具体实现。 - Executors
Executors 是 个 静 态 工 厂 类 , 它 通 过 静 态 工 厂 方 法 返 回 ExecutorService、ScheduledExecutorService 等线程池实例对象,这些静态工厂方法可以理解为一些快捷的创建线程池的方法。
3. Executors创建线程池
java中通过Executor工厂类提供四种快捷创建线程池的方法,如下:
方法名 | 功能简介 |
---|---|
newSingleThreadExecutor() | 创建只有一个线程的线程池 |
newFixedThreadPool(int nThreads) | 创建固定大小的线程池 |
newCachedThreadPool() | 创建一个不限制线程数量的线程池,任何提交的任务都将立即执行,但是空闲线程会得到及时回收 |
newScheduledThreadPool() | 创建一个可定期或者延时执行任务的线程池 |
3.1 newSingleThreadExecutor
创建单线程化线程池该线程池中只有一个线程来工作,此线程池能保证所有任务按照指定的顺序执行
public static final int SLEEP_GAP = 500;
public static final int MAX_TURN = 5;
public static CountDownLatch countDownLatch = new CountDownLatch(MAX_TURN);
public static class TargetTask implements Runnable {
static AtomicInteger taskNo = new AtomicInteger(1);
protected String taskName;
public TargetTask() {
taskName = "task-" + taskNo.get();
taskNo.incrementAndGet();
}
public void run() {
System.out.println("任务:" + taskName + " doing");
try {
Thread.sleep(SLEEP_GAP);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(taskName + " 运行结束.");
countDownLatch.countDown();
}
@Override
public String toString() {
return "TargetTask{" + taskName + '}';
}
}
@Test
public void testSingleThreadExecutor() {
ExecutorService pool = Executors.newSingleThreadExecutor();
for (int i = 0; i < MAX_TURN; i++) {
pool.execute(new TargetTask());//相当于新建线程并使线程start()
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// //关闭线程池
pool.shutdown();
}
运行结果
任务:task-1 doing
task-1 运行结束.
任务:task-2 doing
task-2 运行结束.
任务:task-3 doing
task-3 运行结束.
任务:task-4 doing
task-4 运行结束.
任务:task-5 doing
task-5 运行结束.
特点:
- 存活时间无限
- 任务实例会进入无界的阻塞队列
3.2 newFixedThreadPool
创建固定数量的线程池其唯一的参数用于设置池中线程的“固定数量”。
public static final int SLEEP_GAP = 500;
public static final int MAX_TURN = 5;
public static CountDownLatch countDownLatch = new CountDownLatch(MAX_TURN);
public static class TargetTask implements Runnable {
static AtomicInteger taskNo = new AtomicInteger(1);
protected String taskName;
public TargetTask() {
taskName = "task-" + taskNo.get();
taskNo.incrementAndGet();
}
public void run() {
System.out.println("任务:" + taskName + " doing");
try {
Thread.sleep(SLEEP_GAP);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(taskName + " 运行结束.");
countDownLatch.countDown();
}
@Override
public String toString() {
return "TargetTask{" + taskName + '}';
}
}
@Test
public void testNewFixedThreadPool() throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(3);//核心线程设置为3个
for (int i = 0; i < MAX_TURN; i++) {
pool.execute(new TargetTask());
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//关闭线程池
pool.shutdown();
}
运行结果
任务:task-1 doing
任务:task-3 doing
任务:task-2 doing
task-2 运行结束.
task-1 运行结束.
task-3 运行结束.
任务:task-5 doing
任务:task-4 doing
task-4 运行结束.
task-5 运行结束.
特点:
- 没有达到“固定数量”,每一次提交会创建一个新线程,直到达到线程的"固定数量"
- 一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
- 新来的执行目标实例会进入无界的阻塞队列
3.3 newCachedThreadPool
创建可缓存线程池如果线程内的某些线程无事可干成为空闲线程,“可缓存线程池”可灵活收回这些空闲线程。
public static final int SLEEP_GAP = 500;
public static final int MAX_TURN = 5;
public static CountDownLatch countDownLatch = new CountDownLatch(MAX_TURN);
public static class TargetTask implements Runnable {
static AtomicInteger taskNo = new AtomicInteger(1);
protected String taskName;
public TargetTask() {
taskName = "task-" + taskNo.get();
taskNo.incrementAndGet();
}
public void run() {
System.out.println("任务:" + taskName + " doing");
try {
Thread.sleep(SLEEP_GAP);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(taskName + " 运行结束.");
countDownLatch.countDown();
}
@Override
public String toString() {
return "TargetTask{" + taskName + '}';
}
}
@Test
public void testNewCachedThreadPool() throws InterruptedException {
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < MAX_TURN; i++) {
pool.execute(new TargetTask());
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//关闭线程池
pool.shutdown();
}
运行结果
任务:task-1 doing
任务:task-3 doing
任务:task-2 doing
任务:task-4 doing
任务:task-5 doing
task-5 运行结束.
task-4 运行结束.
task-3 运行结束.
task-2 运行结束.
task-1 运行结束.
特点:
- 在接收新任务时,如果池内所有线程都在运行,此线程池会添加新线程来处理任务。
- 线程池的大小不限,完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小
- 如果空闲线程60秒内不执行任务将会被收回。
- 弊端:线程池没有数量限制,如果大量的任务执同时提交,可能导致创线程过多会而导致资源耗尽。
3.4 newScheduledThreadPool
创建可调度线程池一个提供“延时”和“周期性”任务的调度功能的ScheduledExecutorService类型的线性池。用法如下:
//方法一:创建一个池内仅含有一条线程的线性池
public static ScheduledExecutorService newSingleThreadScheduledExecutor();
//方法二:创建一个可调度线程池,池内含有 N 条线程, N 的值为输入参数 corePoolSize
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) ;
public static final int SLEEP_GAP = 500;
public static final int MAX_TURN = 5;
public static CountDownLatch countDownLatch = new CountDownLatch(MAX_TURN);
public static class TargetTask implements Runnable {
static AtomicInteger taskNo = new AtomicInteger(1);
protected String taskName;
public TargetTask() {
taskName = "task-" + taskNo.get();
taskNo.incrementAndGet();
}
public void run() {
System.out.println("任务:" + taskName + " doing");
try {
Thread.sleep(SLEEP_GAP);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(taskName + " 运行结束.");
countDownLatch.countDown();
}
@Override
public String toString() {
return "TargetTask{" + taskName + '}';
}
}
@Test
public void testNewScheduledThreadPool() {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
for (int i = 0; i < 2; i++) {
pool.scheduleAtFixedRate(new TargetTask(),
0, 5000, TimeUnit.MILLISECONDS);
}
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//关闭线程池
pool.shutdown();
}
运行结果
任务:task-1 doing
任务:task-2 doing
task-1 运行结束.
task-2 运行结束.//间隔5秒再打印
任务:task-1 doing
任务:task-2 doing
task-2 运行结束.
task-1 运行结束.
任务:task-1 doing
任务:task-2 doing
task-2 运行结束.
task-1 运行结束.
两种方法:
1.scheduleAtFixedRate 是从任务开始时算起
2.scheduleWithFixedDelay是从任务结束时算起
4. 线程池的标准创建
大部分企业的开服规范都会禁止使用快捷线程池,要求通过标准构造器ThreadpoolExecutor去构造工作线性池,
使用标准构造器,构造一个普通的线程池
public ThreadPoolExecutor(
int corePoolSize, 核心线程数,即使线程空闲(Idle),也不会回收;
int maximumPoolSize, 线程数的上限;
long keepAliveTime, TimeUnit unit, 线程最大空闲(Idle)时长
BlockingQueue workQueue, 任务的排队队列
ThreadFactory threadFactory, 新线程的产生方式
RejectedExecutionHandler handler) 拒绝策略
-
corePoolSize 核心线程池数,maximumPoolSize 最大线程数。线程池规则如下:
- 当有新任务加入时,并且当前工作线程数少于corePoolSize时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理该请求,直到线程数达到 corePoolSize
- 当前工作线程数多于 corePoolSize 数量,但小于 maximumPoolSize 数量,则仅当任务排队队列已满时,才会创建新线程。
- corePoolSize 和 maximumPoolSize 不 仅 能 在 线 程 池 构 造 时 设 置 , 也 可 以 使 用setCorePoolSize 和 setMaximumPoolSize 两个方法进行动态更改。
-
BlockingQueue(阻塞队列)
实例用于暂时接收到的异步任务,如果线程池的核心线程都在忙,则所接收到的目标任务,缓存在阻塞队列中。 -
KeepAliveTime(存活时间)
-
Unit(时间单位)
-
handler(拒绝策略)
5. 线程池的任务调度流程
(1)当前线程数小于核心线程池数量,执行器总是优先创建一个任务线程
(2)线程池中任务数大于核心线程池数,任务将被加入到阻塞队列中,一直到阻塞队列满。
(3)当完成一个任务的执行时,执行器总是从阻塞队列中取任务并开始执行,直到阻塞队列为空
(4)在核心线程池数量已经用完、阻塞队列也已经满的场景下,如果线程池接收到新的任务,将会为新任务创建一个线程(非核心线程),并且立即开始执行新任务。
(5)在核心线程都用完、阻塞队列已满的情况下,一直会创建新线程去执行新任务,直到池内的线程总数超出 maximumPoolSize。如果线程池的线程总数超时 maximumPoolSize,则线程池会拒绝接收任务,当新任务过来时,会为新任务执行拒绝策略
public static final int SLEEP_GAP = 500;
public static final int MAX_TURN = 5;
public static CountDownLatch countDownLatch = new CountDownLatch(MAX_TURN);
public static class TargetTask implements Runnable {
static AtomicInteger taskNo = new AtomicInteger(1);
protected String taskName;
public TargetTask() {
taskName = "task-" + taskNo.get();
taskNo.incrementAndGet();
}
public void run() {
System.out.println("任务:" + taskName + " doing");
try {
Thread.sleep(SLEEP_GAP);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(taskName + " 运行结束.");
countDownLatch.countDown();
}
@Override
public String toString() {
return "TargetTask{" + taskName + '}';
}
}
@Test使用标准的构造器,构造一个普通的线程池
public void testThreadPoolExecutor() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, //corePoolSize
4, //maximumPoolSize
100, //keepAliveTime
TimeUnit.SECONDS, //unit
new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.CallerRunsPolicy());//workQueue
for (int i = 0; i < 6; i++) {
final int taskIndex = i;
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " taskIndex = " + taskIndex);
try {
Thread.sleep(10000);
// Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
while (true) {
//每隔 1 秒,输出线程池的工作任务数量、总计的任务数量
System.out.printf("- activeCount: %d - taskCount: %d\r\n", executor.getActiveCount(), executor.getTaskCount());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果
pool-1-thread-2 taskIndex = 1
pool-1-thread-1 taskIndex = 0
pool-1-thread-3 taskIndex = 4
pool-1-thread-4 taskIndex = 5
- activeCount: 4 - taskCount: 6
- activeCount: 4 - taskCount: 6
- activeCount: 4 - taskCount: 6
- activeCount: 4 - taskCount: 6
pool-1-thread-1 taskIndex = 2
pool-1-thread-4 taskIndex = 3
- activeCount: 2 - taskCount: 6
- activeCount: 2 - taskCount: 6
- activeCount: 2 - taskCount: 6
- activeCount: 0 - taskCount: 6
6. 线程池的拒绝策略
任务被拒绝有两种情况:
- 线程池已经被关闭。
- 工作队列已满且 maximumPoolSize 已满。
无 论 以 上 哪 种 情 况 任 务 被 拒 , 线 程 池 都 会 调 用RejectedExecutionHandler
实 例 的rejectedExecution
方法。RejectedExecutionHandler
是拒绝策略的接口, JUC 为该接口提供了以下几种实现:- AbortPolicy
- DiscardPolicy
- DiscardOldestPolicy
- CallerRunsPolicy
- 自定义策略
6.1 AbortPolicy
直接抛出异常RejectedExecutionException
默认的拒绝策略
6.2 DiscardPolicy
新任务会直接被丢掉,并且不会有任何异常抛出。
6.3 DiscardOldestPolicy
抛弃最老的任务策略,会将最早进入队列的任务抛弃后,新来的任务再尝试入队
6.4 CallerRunsPolicy
调用者执行策略。在新任务被添加到线程池时,如果添加失败,那么提交任务线程会自己去执行该任务,不会使用线程池中的线程去执行新任务。
7. 线程池的状态
有五种具体为:
- RUNNING:线程池创建之后的初始状态,这种状态下可以执行任务。
- SHUTDOWN:该状态下线程池不再接受新任务,但是会将工作队列中的任务执行完毕。
- STOP:该状态下线程池不再接受新任务,也不会处理工作队列中的剩余任务,并且将会中断所有工作线程。
- TIDYING:该状态下所有任务都已终止或者处理完成,将会执行terminated( )钩子方法。
- TERMINATED:执行完terminated( )钩子方法之后的状态。
线程池的状态转换规则为:
(1)线程池创建之后状态为 RUNNING。
(2)执行线程池的 shutdown 实例方法,会使线程池状态从 RUNNING 转变为 SHUTDOWN。
(3)执行线程池的 shutdownNow 实例方法,会使线程池状态从 RUNNING 转变为 STOP。
(4)当线程池处于 SHUTDOWN 状态,执行器 shutdownNow 方法,会将其状态转变为 STOP状态。
(5)等待线程池的所有工作线程停止,工作队列清空之后,线程池状态会从 STOP 转变为TIDYING。
(6)执行完 terminated( ) 钩子方法之后,线程池状态从 TIDYING 转变为 TERMINATED 。