1. 引言:为什么需要多线程?
在现代计算机架构中,多核CPU已成为主流。为了充分利用硬件资源,提升系统吞吐量和响应速度,多线程编程成为Java开发者必备的核心技能。然而,线程的创建、调度和同步也带来了复杂性——竞态条件、死锁、内存可见性等问题如影随形。本文将深入剖析Java多线程的核心机制,并给出高并发场景下的实战解决方案。
2. 线程基础与生命周期
2.1 线程的创建方式
// 方式1:继承Thread类
class MyThread extends Thread {@Overridepublic void run() {System.out.println("Thread running");}
}// 方式2:实现Runnable接口(推荐)
Runnable task = () -> System.out.println("Runnable task");
new Thread(task).start();// 方式3:FutureTask + Callable(支持返回值)
Callable<Integer> callable = () -> 42;
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
System.out.println(futureTask.get()); // 输出42
为什么推荐Runnable?
- 避免单继承限制
- 更符合面向对象设计原则(任务与执行分离)
2.2 线程状态流转
3. 线程同步:锁的进化论
3.1 synchronized的底层原理
public class Counter {private int count;// 同步方法:锁是当前实例对象public synchronized void increment() {count++;}// 同步块:锁是指定对象public void add(int value) {synchronized(this) {count += value;}}
}
锁升级过程:
无锁 → 偏向锁(单线程) → 轻量级锁(CAS自旋) → 重量级锁(OS互斥量)
3.2 ReentrantLock的进阶特性
ReentrantLock lock = new ReentrantLock(true); // 公平锁
Condition condition = lock.newCondition();void doWork() {lock.lock();try {while (!conditionMet) {condition.await(); // 释放锁并等待}// 临界区操作condition.signalAll();} finally {lock.unlock();}
}
对比synchronized优势:
- 可中断的锁获取
- 超时获取锁
- 公平锁支持
- 多条件变量(Condition)
3.3 StampedLock:读多写少场景的终极武器
StampedLock lock = new StampedLock();// 乐观读
long stamp = lock.tryOptimisticRead();
if (!lock.validate(stamp)) {stamp = lock.readLock(); // 升级为悲观读try {// 读取数据} finally {lock.unlockRead(stamp);}
}// 写锁
long writeStamp = lock.writeLock();
try {// 修改数据
} finally {lock.unlockWrite(writeStamp);
}
4. 并发工具库:JUC的瑰宝
4.1 同步屏障:CyclicBarrier vs CountDownLatch
// CyclicBarrier(可重复使用)
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有线程到达屏障"));ExecutorService pool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {pool.submit(() -> {// 处理任务barrier.await(); // 等待其他线程});
}// CountDownLatch(一次性)
CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> {// 任务1latch.countDown();
}).start();
// ...启动其他线程
latch.await(); // 阻塞直到计数器归零
4.2 CompletableFuture:异步编程新范式
CompletableFuture.supplyAsync(() -> fetchDataFromDB()).thenApply(data -> processData(data)).thenAccept(result -> sendResult(result)).exceptionally(ex -> {System.err.println("Error: " + ex);return null;});
优势:
- 链式调用
- 异常处理
- 组合多个Future
- 超时控制
5. 线程池:高并发的基石
5.1 ThreadPoolExecutor核心参数
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, // corePoolSize10, // maximumPoolSize60, TimeUnit.SECONDS, // keepAliveTimenew LinkedBlockingQueue<>(100), // workQueuenew ThreadFactoryBuilder().setNameFormat("worker-%d").build(),new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
四种拒绝策略对比:
- AbortPolicy:抛出RejectedExecutionException(默认)
- CallerRunsPolicy:由调用线程执行任务
- DiscardPolicy:静默丢弃任务
- DiscardOldestPolicy:丢弃队列最旧任务
5.2 线程池监控与调优
// 监控关键指标
executor.getPoolSize(); // 当前线程数
executor.getActiveCount(); // 活动线程数
executor.getQueue().size(); // 队列积压数
调优建议:
- CPU密集型:线程数 = CPU核心数 + 1
- IO密集型:线程数 = CPU核心数 * (1 + 平均等待时间/计算时间)
- 使用有界队列防止内存溢出
- 通过JMX实现动态参数调整
6. 高并发陷阱与解决方案
6.1 死锁检测与预防
死锁产生的四个必要条件:
- 互斥条件
- 请求与保持
- 不可剥夺
- 循环等待
诊断工具:
jstack <pid>
- VisualVM线程分析
- Arthas的
thread -b
命令
6.2 ThreadLocal的内存泄漏
// 正确使用方式
try (ExecutorService pool = Executors.newSingleThreadExecutor()) {ThreadLocal<Connection> connHolder = new ThreadLocal<>();pool.submit(() -> {try {connHolder.set(getConnection());// 使用连接} finally {connHolder.remove(); // 必须手动清理}});
}
最佳实践:
- 使用static final修饰ThreadLocal实例
- 配合try-finally确保remove()执行
- 考虑使用FastThreadLocal(Netty优化版)
7. 性能优化:从理论到实践
7.1 锁粒度优化
错误示例:
public class BigLockCounter {private int a, b;public synchronized void incrementA() { a++; }public synchronized void incrementB() { b++; }
}
优化方案:
public class FineGrainedCounter {private final Object lockA = new Object();private final Object lockB = new Object();private int a, b;public void incrementA() {synchronized(lockA) { a++; }}public void incrementB() {synchronized(lockB) { b++; }}
}
7.2 无锁编程实战
// 使用LongAdder替代AtomicLong
LongAdder counter = new LongAdder();
IntStream.range(0, 1000).parallel().forEach(i -> counter.increment());// 使用ConcurrentHashMap的compute方法
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.compute("key", (k, v) -> (v == null) ? 1 : v + 1);
性能对比(JMH测试):
Benchmark Mode Cnt Score Error Units
LockVsCAS.lockBased thrpt 5 1234.56 ± 67.89 ops/ms
LockVsCAS.casBased thrpt 5 98765.43 ± 1234.56 ops/ms
8. 未来展望:虚拟线程(Project Loom)
Java 19引入的虚拟线程(Virtual Thread)将彻底改变多线程编程范式:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {IntStream.range(0, 10_000).forEach(i -> executor.submit(() -> {Thread.sleep(Duration.ofSeconds(1));return i;}));
} // 这里会自动等待所有任务完成
核心优势:
- 轻量级:数千个虚拟线程对应少量OS线程
- 无回调地狱:保持同步代码风格
- 与现有API兼容
9. 结语
掌握Java多线程开发需要深入理解内存模型、锁机制和并发工具类。在高并发场景下,建议:
- 优先使用并发容器(如ConcurrentHashMap)
- 合理选择同步机制(synchronized vs Lock)
- 严格监控线程池状态
- 通过压测寻找性能瓶颈
- 关注Java并发生态的新发展
真正的并发大师,不仅懂得如何创建线程,更懂得如何让线程优雅协作。