Java 多线程开发
Java 多线程开发是 Java 并发编程的核心内容,用于实现程序的并发执行,提高 CPU 利用率和系统吞吐量。以下是 Java 多线程开发的系统性介绍:
线程
一、为什么需要多线程?
- 提升性能:充分利用多核 CPU 资源。
- 响应性:UI 线程不阻塞(如 Swing/JavaFX 中后台任务)。
- 异步处理:如网络请求、文件 I/O、定时任务等。
- 建模现实问题:如生产者-消费者、银行转账等并发场景。
二、创建线程的两种基本方式
1. 实现 Runnable 接口(推荐)
Runnable task = () -> System.out.println("Hello from thread: " + Thread.currentThread().getName());
Thread t = new Thread(task);
t.start();
2. 继承 Thread 类(不推荐)
class MyThread extends Thread {
public void run() {
System.out.println("Running in " + getName());
}
}
new MyThread().start();
✅ 最佳实践:优先使用
Runnable+Thread或更高级的并发工具(如线程池)。
三、线程的生命周期(6 种状态)
| 状态 | 说明 |
|---|---|
NEW |
线程刚创建,未调用 start() |
RUNNABLE |
可运行(包括正在运行或等待 CPU 时间片) |
BLOCKED |
等待获取监视器锁(如进入 synchronized 块) |
WAITING |
无限期等待(如 wait(), join(), LockSupport.park()) |
TIMED_WAITING |
限时等待(如 sleep(1000), wait(1000)) |
TERMINATED |
线程执行完毕或异常终止 |
可通过 Thread.getState() 查看状态。
四、线程同步机制(解决共享资源竞争)
1. synchronized 关键字
- 修饰实例方法:锁当前对象(
this) - 修饰静态方法:锁类的
Class对象 - 同步代码块:
synchronized(obj) { ... },可指定任意锁对象
⚠️ 静态与非静态
synchronized方法不互斥(锁对象不同)。
2. volatile 关键字
- 保证变量的可见性和禁止指令重排序
- 不保证原子性(如
i++仍需同步)
3. java.util.concurrent.locks.Lock
- 更灵活的显式锁(如
ReentrantLock) - 支持尝试获取锁、超时、公平锁等
Lock lock = new ReentrantLock();
lock.lock();
try {
// 临界区
} finally {
lock.unlock(); // 必须在 finally 中释放
}
五、线程间通信
1. wait() / notify() / notifyAll()
- 必须在
synchronized块中调用 wait()释放锁并进入等待队列notify()唤醒一个等待线程
2. Condition(配合 Lock 使用)
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 等待
lock.lock();
try {
condition.await();
} finally {
lock.unlock();
}
// 唤醒
condition.signal(); // 或 signalAll()
六、高级并发工具(java.util.concurrent 包)
1. 线程池(核心!)
避免频繁创建/销毁线程,提升性能。
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> {
// 任务逻辑
});
executor.shutdown();
常用工厂方法:
newFixedThreadPool(n):固定大小线程池newCachedThreadPool():弹性线程池(适合短任务)newSingleThreadExecutor():单线程顺序执行- 生产建议:使用
ThreadPoolExecutor自定义参数(避免Executors的隐藏风险)
2. 并发集合
ConcurrentHashMap:线程安全的 HashMapCopyOnWriteArrayList:读多写少场景BlockingQueue:如ArrayBlockingQueue,LinkedBlockingQueue(常用于生产者-消费者)
3. 同步工具类
CountDownLatch:等待 N 个任务完成CyclicBarrier:多个线程互相等待到达屏障点Semaphore:控制同时访问资源的线程数CompletableFuture:异步编程(Java 8+)
七、常见问题与最佳实践
❌ 常见陷阱
- 死锁(多个锁顺序不一致)
- 活锁 / 饥饿
- 内存可见性问题(忘记
volatile或同步) - 过度同步(性能下降)
✅ 最佳实践
- 优先使用并发工具类(如
ConcurrentHashMap而非Collections.synchronizedMap) - 缩小同步范围:只同步必要代码
- 避免在构造函数中启动线程
- 使用线程池管理线程,而非手动创建
- 命名线程:便于调试(
new Thread(runnable, "MyWorker")) - 处理中断:响应
InterruptedException,不要忽略
八、示例:生产者-消费者模型(使用 BlockingQueue)
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
// 生产者
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 20; i++) {
queue.put(i); // 阻塞直到有空间
System.out.println("Produced: " + i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 消费者
Thread consumer = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
Integer item = queue.take(); // 阻塞直到有元素
System.out.println("Consumed: " + item);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
producer.start();
consumer.start();
总结
Java 多线程开发从基础线程创建到高级并发工具,是一个逐步演进的过程。现代 Java 开发应:
“少用原始 Thread,多用 Executor;少用 synchronized,多用并发集合和原子类;善用 CompletableFuture 实现异步非阻塞。”
虚拟线程
Java 虚拟线程(Virtual Threads) 是 Java 并发编程领域自 JDK 21(2023年发布)以来最具革命性的特性,属于 Project Loom 项目的核心成果。到 2026 年,它已成为高并发后端开发的事实标准,彻底改变了 Java 处理百万级并发的模式。
以下是基于 2026 年最新生产实践的深度解析与避坑指南:
🚀 核心概念:什么是虚拟线程?
传统 Java 线程(平台线程)是 1:1 映射 到操作系统内核线程的,创建成本高(约 1MB 内存)、上下文切换开销大。
虚拟线程 则是 M:N 映射(M 个虚拟线程运行在 N 个平台线程上):
- 轻量级:每个虚拟线程仅占用约 几百字节 栈内存(初始值),可轻松创建百万级实例。
- 用户态调度:由 JVM 直接调度,无需陷入内核态,极大降低切换成本。
- 阻塞即挂起:当虚拟线程执行 I/O 阻塞(如
sleep,socket.read)时,JVM 会自动将其卸载(unmount),释放底层的载体线程(Carrier Thread)去执行其他任务,实现“同步代码,异步效果”。
💡 一句话总结:让你用写同步代码的习惯(
Thread.sleep,BlockingQueue),获得接近响应式编程(Reactive)的高吞吐能力。
🛠️ 2026 年最佳实践代码
1. 快速启动(JDK 21+)
// 方式一:直接启动(适合临时任务)
Thread.startVirtualThread(() -> {
System.out.println("Hello from Virtual Thread: " + Thread.currentThread());
});
// 方式二:使用 ExecutorService(推荐,便于管理生命周期)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 1_000_000).forEach(i -> {
executor.submit(() -> {
// 模拟 I/O 阻塞
Thread.sleep(1000);
return i;
});
});
} // try-with-resources 自动等待所有任务完成并关闭
2. Spring Boot 3.x 集成(2026 主流配置)
在 application.properties 中开启虚拟线程支持,Tomcat/Jetty 将自动使用虚拟线程处理请求:
spring.threads.virtual.enabled=true
# 可选:调整载体线程池大小(默认 CPU 核心数)
server.tomcat.threads.max=200
✅ 效果:无需修改 Controller 代码,原本阻塞的 JDBC/Redis 调用现在能支撑十倍以上的并发量。
⚠️ 2026 年生产环境“避坑”指南(关键!)
根据近期社区反馈和架构师实战总结,以下场景需特别警惕:
1. ❌ 严禁用于 CPU 密集型任务
虚拟线程的优势在于 I/O 等待时的挂起。如果是纯计算任务(如视频编码、复杂数学运算):
- 现象:CPU 占用率飙升,性能甚至低于传统线程池。
- 原因:虚拟线程不会主动让出 CPU,大量计算任务会占满载体线程,导致调度器频繁空转。
- 对策:CPU 密集型任务请继续使用
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())。
2. 🔒 慎用 synchronized 和 JNI 阻塞
- 问题:
synchronized锁和某些 native 方法(JNI)会导致虚拟线程 Pinned(钉住) 在载体线程上,无法挂起。 - 后果:如果大量虚拟线程被钉住,载体线程池会被耗尽,系统退化为传统线程模型,失去高并发优势。
- 解决方案:
- 使用
java.util.concurrent.locks.ReentrantLock替代synchronized。 - 检查第三方库是否包含长时间运行的 native 阻塞调用。
- 开启 JVM 参数
-Djdk.tracePinnedThreads=full在开发环境排查钉住线程。
- 使用
3. 🧵 ThreadLocal 的内存泄漏风险
- 风险:虚拟线程数量巨大(百万级),如果每个线程都绑定大量
ThreadLocal数据(如大型用户上下文对象),会导致堆内存爆炸。 - 新方案:推荐使用 JDK 24+ 正式稳定的 Scoped Values(作用域值)。
static final ScopedValue<String> USER_ID = ScopedValue.newInstance(); ScopedValue.where(USER_ID, "12345").run(() -> { // 在此作用域内共享数据,无内存泄漏风险,且更安全 processRequest(); });
4. 📉 监控与调试的变化
- 挑战:传统的
jstack或线程 Dump 会显示数百万行日志,难以分析。 - 对策:
- 使用 Java Flight Recorder (JFR) 配合新版 JDK 的虚拟线程事件过滤。
- 关注指标:虚拟线程创建速率、平均存活时间、Pinned 线程比例。
🆚 虚拟线程 vs WebFlux (响应式编程)
| 维度 | 虚拟线程 (Virtual Threads) | WebFlux / Reactive |
|---|---|---|
| 编程模型 | 同步阻塞 (直观,易调试) | 异步回调/流式 (陡峭学习曲线) |
| 生态兼容 | 完美兼容 (所有旧库可直接用) | 需响应式驱动库 (R2DBC, WebClient) |
| 性能表现 | I/O 密集型极高,略低于极致优化的 Reactor | 极低延迟场景略优,但复杂链路难维护 |
| 2026 趋势 | 首选方案 (90% 场景) | 仅限超低延迟/流式处理特定场景 |
🎯 结论:除非你有极端的低延迟需求或处理无限数据流,否则 2026 年请优先选择虚拟线程。它能以最小的重构成本带来最大的收益。
📈 未来展望 (JDK 26+)
- 结构化并发 (Structured Concurrency):
StructuredTaskScope将进入正式版,解决多线程任务的生命周期管理难题(如自动取消子任务、统一异常处理)。 - 全景可观测性:新一代 APM 工具(SkyWalking, Arthas)已全面适配虚拟线程追踪,能清晰展示百万级线程的调用链。
更多推荐




所有评论(0)