最近正在复习Java八股,所以会将一些热门的八股问题,结合ai与自身理解写成博客便于记忆
本文将从以上10个经典面试问题来做juc多线程的解析
一、线程状态与流转机制
1. 六种线程状态(Java定义)
public enum State {
NEW, // 新建未启动
RUNNABLE, // 可运行(包含就绪和运行中)
BLOCKED, // 被阻塞(等待监视器锁)
WAITING, // 无限期等待(无时限的wait/join/park)
TIMED_WAITING, // 限期等待(带时限的sleep/wait/join/park)
TERMINATED // 终止
}
2.关键转换场景:
start():NEW → RUNNABLE
获取锁失败:RUNNABLE → BLOCKED
Object.wait():RUNNABLE → WAITING
Thread.sleep(ms)`:RUNNABLE → TIMED_WAITING
任务结束/异常:RUNNABLE → TERMINATED
二、线程创建方式对比
1. 基础创建方式
// 方式1:继承Thread类
class MyThread extends Thread {
public void run() {
System.out.println("Thread running");
}
}
new MyThread().start();
// 方式2:实现Runnable接口
new Thread(() -> System.out.println("Runnable running")).start();
// 方式3:实现Callable接口(可获取返回值)
FutureTask<String> task = new FutureTask<>(() -> "Callable result");
new Thread(task).start();
String result = task.get();
2. 生产环境推荐
线程池创建(推荐所有场景)
CompletableFuture(Java8+异步编程)
虚拟线程(Java19+轻量级线程)
三、多线程典型应用场景
1. 高并发处理
电商秒杀系统
即时通讯消息推送
金融交易订单处理
2. 异步任务
```java
// 异步日志记录
executor.execute(() -> logService.saveLog(logEntity));
// 并行计算
List<Future<Result>> futures = IntStream.range(0, 10)
.mapToObj(i -> executor.submit(() -> compute(i)))
.collect(Collectors.toList());
```
3. 定时调度
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
scheduler.scheduleAtFixedRate(() -> {
System.out.println("定时任务执行");
}, 1, 5, TimeUnit.SECONDS);
四、线程池核心机制
1. 七大构造参数
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 工作队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
2. 执行流程图示
提交任务
│
▼
核心线程是否已满?──否─► 创建核心线程执行
│是
▼
工作队列是否已满?──否─► 加入队列等待
│是
▼
最大线程是否已满?──否─► 创建临时线程执行
│是
▼
执行拒绝策略
3. 拒绝策略对比
策略类 | 行为 | 适用场景 |
AbortPolicy(默认) | 抛出RejectedExecutionException | 需要感知任务拒绝的场景 |
CallerRunsPolicy | 由提交线程直接执行任务 | 不希望丢失任务的场景 |
DiscardPolicy | 静默丢弃任务 | 可容忍任务丢失的场景 |
DiscardOldestPolicy | 丢弃队列最老任务并重试 | 允许丢弃旧任务的场景 |
五、线程池配置实践
1. 线程数设置公式
CPU密集型:corePoolSize = CPU核数 + 1
IO密集型:corePoolSize = CPU核数 × (1 + 平均等待时间/平均计算时间)
int cpuCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
cpuCores * 2, // 核心线程数
cpuCores * 4, // 最大线程数
60, // 空闲线程存活时间(s)
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000), // 有界队列
new NamedThreadFactory("app-thread"), // 自定义线程命名
new CallerRunsPolicy() // 降级策略
);
2. 避免使用Executors
问题案例:
// 可能导致OOM(无界队列)
ExecutorService executor = Executors.newFixedThreadPool(10);
// 最大线程数过大(Integer.MAX_VALUE)
ExecutorService executor = Executors.newCachedThreadPool();
六、生产环境监控指标
1. 关键监控项
指标 | 监控方式 | 健康标准 |
活跃线程数 | getActiveCount() | ≤ maximumPoolSize |
任务队列大小 | getQueue().size() | ≤ 队列容量80% |
已完成任务数 | getCompletedTaskCount() | 持续增长 |
拒绝任务数 | 自定义RejectedExecutionHandler | =0(理想情况) |
线程空闲率 | (poolSize-activeCount)/poolSize | 20%~80%波动 |
七、常见问题解答
1:线程池为什么先填满队列再创建线程?
设计考量:
1. 线程创建销毁有开销
2. 任务排队通常比新建线程更高效
3. 避免线程数剧烈波动
2:如何选择工作队列?
选型指南:
ArrayBlockingQueue:固定大小,内存控制严格
LinkedBlockingQueue:无界/有界,吞吐量高
SynchronousQueue:直接传递,避免排队
PriorityBlockingQueue:优先级任务
3:核心线程为什么不会被回收?
设计意图:
1. 保持基础处理能力
2. 避免频繁创建销毁核心线程
3. 可通过`allowCoreThreadTimeOut(true)`改变默认行为