1. 引言:为什么需要多线程?
在现代计算机架构中,多核CPU已成为主流。为了充分利用硬件资源,提升系统吞吐量和响应速度,多线程编程成为Java开发者必备的核心技能。然而,线程的创建、调度和同步也带来了复杂性——竞态条件、死锁、内存可见性等问题如影随形。本文将深入剖析Java多线程的核心机制,并给出高并发场景下的实战解决方案。
2. 线程基础与生命周期
2.1 线程的创建方式
// 方式1:继承Thread类
class MyThread extends Thread {
@Override
public 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(() -> {
// 任务1
latch.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, // corePoolSize
10, // maximumPoolSize
60, TimeUnit.SECONDS, // keepAliveTime
new LinkedBlockingQueue<>(100), // workQueue
new 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并发生态的新发展
真正的并发大师,不仅懂得如何创建线程,更懂得如何让线程优雅协作。