本文纲要
-
线程的生命周期与状态
概念介绍:线程的六种状态
状态转换图 (Mermaid) -
线程池基本原理
为什么需要线程池
线程池的思想模型 -
使用
Executors创建线程池
newCachedThreadPool创建弹性线程池
newFixedThreadPool创建固定大小线程池
代码演示与参数说明 -
深入
ThreadPoolExecutor自定义线程池
七个核心参数的故事映射
参数详解 (时间单位、队列、工厂)
任务拒绝策略的触发时机 -
四种拒绝策略实战
AbortPolicy
DiscardPolicy
DiscardOldestPolicy
CallerRunsPolicy -
项目代码结构
线程的生命周期与状态
在多线程编程中,每一个线程从创建到消亡,都会经历一系列状态。Java 虚拟机 (JVM) 中定义了线程的 六种状态,注意,虚拟机并未定义“运行状态”,因为运行状态是线程与操作系统 CPU 直接交互时的状态。
- 新建 (NEW):创建线程对象,尚末调用
start()。 - 就绪 (RUNNABLE):调用
start()后,线程获得执行资格,等待 CPU 调度。 - 运行 (JVM 未单独定义):线程真正获得 CPU 时间片,执行代码。
- 阻塞 (BLOCKED):线程试图获取一个锁(如
synchronized),但锁已被其他线程持有。 - 等待 (WAITING):线程执行了
wait()方法,进入无限等待,直到被notify/notifyAll唤醒。 - 计时等待 (TIMED_WAITING):线程执行了
sleep(millis)或带超时的wait(millis),在指定时间内等待,时间结束后回到就绪态。 - 结束 (TERMINATED):线程的
run()方法执行完毕,线程对象销毁。
这些状态在 Thread 类的内部枚举 State 中定义:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED。
下面是线程状态转换图:
注意:虚拟机中没有直接定义“运行”状态,而是将“就绪”和“运行”统一表示为 RUNNABLE。
线程池基本原理
1 ) 为什么需要线程池?
如果每次执行任务都手动 new Thread 然后启动,会有两个问题:
- 频繁创建和销毁线程,开销大(浪费时间);
- 线程对象无法复用,浪费系统资源(浪费内存)。
线程池采用“池化思想”,预先创建一定数量的线程放入池中,有任务时从池中取线程执行,执行完毕归还而不是销毁,从而复用线程,减少开销。
2 ) 工作原理比喻
可以把线程池比作一个“碗柜”,线程对象就像是“碗”:
- 初始时碗柜是空的。
- 第一次需要线程(吃饭)时,去创建一个(买碗),用完放回碗柜。
- 以后再需要线程时,直接从碗柜取一个空闲的,不再重复创建。
对应线程池流程:
使用 Executors 创建线程池
Java 提供了 Executors 工具类,可以快速创建常用的线程池。
1 ) 创建默认线程池 newCachedThreadPool
创建一个可根据需要创建新线程的线程池,空闲线程存活 60 秒后回收,适合执行大量短期异步任务。
代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
// 创建一个默认的线程池对象. 池子中默认是空的.
ExecutorService executorService = Executors.newCachedThreadPool();
// Executors --- 帮助我们创建线程池
// ExecutorService --- 帮助我们控制线程池
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + " 在执行了");
});
// Thread.sleep(2000); // 如果主线程休眠,可以看到线程复用效果
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + " 在执行了");
});
executorService.shutdown(); // 关闭线程池
}
}
说明:
submit方法提交任务(Runnable或Callable),线程池自动分配线程执行。
如果第一个任务执行很快,线程被归还后,第二个任务会复用该线程(线程名相同)。
若不调用shutdown(),程序不会退出,因为池中线程还在存活。
2 ) 创建固定大小线程池 newFixedThreadPool
指定池中最大线程数,一旦创建,线程数量固定,空闲线程不会被回收。
代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class MyThreadPoolDemo2 {
public static void main(String[] args) {
// 参数不是初始值而是最大值
ExecutorService executorService = Executors.newFixedThreadPool(10);
ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;
System.out.println(pool.getPoolSize()); // 0
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + " 在执行了");
});
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + " 在执行了");
});
System.out.println(pool.getPoolSize()); // 2
// executorService.shutdown();
}
}
关键点:
newFixedThreadPool(10)创建时,池内线程数为 0(不是初始10个线程)。- 通过
getPoolSize()查看当前线程数量。 - 提交两个任务后,线程数变为 2,不会超过设定的最大值。
深入 ThreadPoolExecutor 自定义线程池
Executors 的静态方法本质上都是通过 ThreadPoolExecutor 实现的。
我们可以直接使用它创建更灵活的线程池。
1 ) 七个核心参数的故事对照
我们用“饭店雇佣服务员”的比喻来理解这七个参数:
| 参数数量 | 参数名称 (饭店比喻) | 线程池参数 | 说明 |
|---|---|---|---|
| 1 | 正式员工数量 | 核心线程数 (corePoolSize) | 一直在岗,即使空闲也不被辞退 |
| 2 | 最大员工数 (正式+临时) | 最大线程数 (maximumPoolSize) | 必须 ≥ 核心线程数 |
| 3 | 临时工空闲后多久辞退 (值) | 空闲线程存活时间 (keepAliveTime) | 数值,如 60 |
| 4 | 时间单位 (秒/分/时) | 时间单位 (TimeUnit) | 例如 TimeUnit.SECONDS |
| 5 | 等待区排队顾客数 | 任务队列 (BlockingQueue) | 当线程数达到核心数且全忙,新任务在队列中等待 |
| 6 | 从哪里招人 | 线程工厂 (ThreadFactory) | 默认使用 Executors.defaultThreadFactory() |
| 7 | 客满排队拒接策略 | 拒绝策略 (RejectedExecutionHandler) | 超过最大线程+队列容量时的处理方式 |
2 ) 构造自定义线程池
项目结构(代码目录):
mythreadpool/
├── MyRunnable.java
├── MyThreadPoolDemo3.java
├── MyThreadPoolDemo4.java
├── MyThreadPoolDemo5.java
├── MyThreadPoolDemo6.java
└── MyThreadPoolDemo7.java
MyRunnable 任务类:
package com.wb.mythreadpool;
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 在执行了");
}
}
使用所有七个参数的线程池:
package com.wb.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo3 {
// 参数一:核心线程数量
// 参数二:最大线程数
// 参数三:空闲线程最大存活时间
// 参数四:时间单位
// 参数五:任务队列
// 参数六:创建线程工厂
// 参数七:任务的拒绝策略
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
2, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位秒
new ArrayBlockingQueue<>(10), // 任务队列容量10
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() // 默认拒绝策略
);
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.shutdown();
}
}
3 ) 参数详解
- 时间单位
TimeUnit:枚举类型,常用TimeUnit.SECONDS(秒)、TimeUnit.HOURS(小时)等,不能直接传字符串。 - 任务队列:当核心线程都在忙,但尚未达到最大线程数时,新任务会先放入队列。队列满且线程数达到最大时,新任务触发拒绝策略。常用
ArrayBlockingQueue。 - 线程工厂:默认工厂
Executors.defaultThreadFactory()会创建普通非守护线程,并设置线程名和优先级。 - 拒绝策略触发公式:
- 当
提交任务数>最大线程数+队列容量时,触发拒绝。
- 当
- 例如:最大线程 5,队列容量 10,提交 16 个任务(5+10=15,16>15)时就会拒绝。
演示代码 (MyThreadPoolDemo4):
package com.wb.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo4 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
5,
2,
TimeUnit.HOURS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// 提交 16 个任务(最大5+队列10=15,16>15),将触发拒绝
for (int i = 1; i <= 16; i++) {
pool.submit(new MyRunnable());
}
pool.shutdown();
}
}
运行将抛出 RejectedExecutionException 异常,表明多余的任务被拒绝。
四种拒绝策略实战
Java 线程池定义了四种 RejectedExecutionHandler 实现类,我们可以通过更换第 7 个参数来选择不同策略。
| 拒绝策略类 | 策略描述 | 是否推荐 |
|---|---|---|
AbortPolicy | 丢弃任务并抛出 RejectedExecutionException | 是 (默认) |
DiscardPolicy | 丢弃任务但不抛异常 | 不推荐 |
DiscardOldestPolicy | 丢弃队列中最旧的任务,然后重新尝试提交当前任务 | 按需使用 |
CallerRunsPolicy | 由调用线程(提交任务的线程)直接执行该任务 | 可调优 |
1 ) AbortPolicy(默认)
代码见上文 MyThreadPoolDemo4,提交数超过阈值时抛出异常,导致程序中断。
2 ) DiscardPolicy – 静默丢弃
package com.wb.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo5 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1,
2,
2,
TimeUnit.HOURS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());
for (int i = 1; i <= 5; i++) {
int y = i;
pool.submit(() -> {
System.out.println(Thread.currentThread().getName() + "-----" + y);
});
}
pool.shutdown();
}
}
结果分析:最大线程 2 + 队列 1 = 3,5 个任务中只有 3 个会被执行,其余 2 个被偷偷丢弃,不会报错。控制台只输出三个任务的信息。
3 ) DiscardOldestPolicy – 丢弃最旧任务
package com.wb.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo6 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1,
2,
2,
TimeUnit.HOURS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 1; i <= 10; i++) {
int y = i;
pool.submit(() -> {
System.out.println(Thread.currentThread().getName() + "-----" + y);
});
}
pool.shutdown();
}
}
效果:同样最多执行 3 个任务,但它会丢弃队列中等待最久的那个旧任务,将新任务加入队列,因此最后执行的任务很可能是较晚提交的任务(如最后一个任务 10 被保留)。
4 ) CallerRunsPolicy – 调用者执行
package com.wb.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo7 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1,
2,
2,
TimeUnit.HOURS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 1; i <= 10; i++) {
int y = i;
pool.submit(() -> {
System.out.println(Thread.currentThread().getName() + "-----" + y);
});
}
pool.shutdown();
}
}
现象:某些任务由 main 线程执行(其他任务由池内线程执行)。因为当线程池和队列都满时,提交任务的线程(此处为主线程)会自己执行该任务,减缓提交速度,起到一定的“流控”作用。
总结
项目代码结构
mythread/
└── src/
└── com/
└── wb/
└── mythreadpool/
├── MyRunnable.java
├── MyThreadPoolDemo.java (newCachedThreadPool)
├── MyThreadPoolDemo2.java (newFixedThreadPool)
├── MyThreadPoolDemo3.java (自定义线程池基础)
├── MyThreadPoolDemo4.java (AbortPolicy 触发)
├── MyThreadPoolDemo5.java (DiscardPolicy)
├── MyThreadPoolDemo6.java (DiscardOldestPolicy)
└── MyThreadPoolDemo7.java (CallerRunsPolicy)
所有示例代码均已给出,可直接运行观察不同线程池和拒绝策略的行为。掌握线程状态和线程池是 Java 高并发编程的基础,建议结合代码动手实践,加深理解。
1283

被折叠的 条评论
为什么被折叠?



