1. 多线程编程基础概念
在计算机科学领域,多线程(Multithreading)是指一个进程中包含多个执行流的技术。每个线程都是CPU调度的基本单位,共享进程的资源(如内存空间、文件描述符等),但拥有独立的程序计数器、寄存器集合和栈空间。
现代操作系统普遍采用抢占式多任务处理机制,线程作为轻量级实体,其创建、切换和销毁的开销远小于进程。以Java为例,当启动一个main()方法时,JVM会创建主线程,开发者可以通过Thread类或Executor框架创建额外线程。
关键理解:线程不是并行执行的充分条件,真正的并行需要多核CPU支持。单核环境下通过时间片轮转模拟"并发"效果。
2. Java线程实现方式对比
2.1 继承Thread类
最基础的实现方式,直接继承java.lang.Thread并重写run()方法:
java复制class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
}
// 启动线程
new MyThread().start();
局限性:
- Java单继承机制导致扩展性受限
- 任务与执行机制强耦合
- 缺少返回值处理和异常捕获机制
2.2 实现Runnable接口
更推荐的实现方式,实现java.lang.Runnable接口:
java复制class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running: " + Thread.currentThread().getName());
}
}
// 通过Thread包装启动
new Thread(new MyRunnable()).start();
优势:
- 避免继承限制,可灵活实现多个接口
- 任务与执行解耦
- 便于线程池管理等高级功能
2.3 实现Callable接口
Java 5+引入的java.util.concurrent.Callable,支持返回值和异常抛出:
java复制class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result from " + Thread.currentThread().getName();
}
}
// 配合ExecutorService使用
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
System.out.println(future.get()); // 获取返回值
executor.shutdown();
核心差异:
| 特性 | Thread | Runnable | Callable |
|---|---|---|---|
| 返回值 | 无 | 无 | 有 |
| 异常处理 | 自行捕获 | 自行捕获 | 可抛出 |
| Java版本 | 1.0 | 1.0 | 5.0 |
| 线程池支持 | 不直接支持 | 支持 | 支持 |
3. JUC核心组件解析
Java并发工具包(java.util.concurrent)提供了一套完善的并发编程工具,主要包含以下核心组件:
3.1 原子类(Atomic)
解决复合操作的原子性问题,如AtomicInteger:
java复制AtomicInteger counter = new AtomicInteger(0);
// 线程安全的递增操作
counter.incrementAndGet();
实现原理基于CAS(Compare-And-Swap)和volatile:
java复制public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
// HotSpot源码中的CAS实现
UNSAFE.compareAndSwapInt(obj, offset, expect, update);
3.2 并发集合
线程安全的集合实现:
- ConcurrentHashMap:分段锁技术
- CopyOnWriteArrayList:写时复制
- BlockingQueue:生产者-消费者模型基础
示例:LinkedBlockingQueue
java复制BlockingQueue<String> queue = new LinkedBlockingQueue<>(10);
// 生产者
queue.put("item");
// 消费者
String item = queue.take();
3.3 锁机制
相比synchronized的关键字,JUC提供更灵活的锁:
java复制ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
while (queue.isEmpty()) {
condition.await(); // 释放锁并等待
}
// 处理资源...
condition.signalAll();
} finally {
lock.unlock();
}
锁优化建议:
- 尽量缩小锁的粒度
- 避免嵌套锁导致的死锁
- 使用tryLock()设置超时时间
- 读写分离场景优先用ReadWriteLock
3.4 线程池(ThreadPoolExecutor)
核心参数配置示例:
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // 核心线程数
8, // 最大线程数
60, // 空闲线程存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 工作队列
new ThreadFactory() { // 线程工厂
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
执行流程:
- 提交任务时优先使用核心线程
- 核心线程忙时任务进入队列
- 队列满时创建非核心线程
- 达到最大线程数后触发拒绝策略
4. 线程同步与通信
4.1 volatile关键字
保证可见性和有序性(非原子性):
java复制class VolatileExample {
private volatile boolean flag = false;
public void writer() {
flag = true; // 写操作
}
public void reader() {
if (flag) { // 读操作
// 保证能看到最新值
}
}
}
内存语义:
- 写volatile变量 → 立即刷新到主内存
- 读volatile变量 → 使本地缓存失效,从主内存读取
4.2 CountDownLatch
多线程协同工具:
java复制CountDownLatch latch = new CountDownLatch(3);
// 工作线程
new Thread(() -> {
// 执行任务...
latch.countDown();
}).start();
// 主线程等待
latch.await(); // 阻塞直到计数器归零
System.out.println("All tasks completed");
典型应用场景:
- 并行任务汇总
- 服务启动依赖检查
- 多测试用例并发执行
4.3 CyclicBarrier
可循环使用的屏障:
java复制CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("All parties arrived");
});
IntStream.range(0, 3).forEach(i -> new Thread(() -> {
try {
System.out.println("Thread " + i + " waiting");
barrier.await(); // 等待其他线程
System.out.println("Thread " + i + " released");
} catch (Exception e) {
e.printStackTrace();
}
}).start());
与CountDownLatch对比:
| 特性 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 重置能力 | 不可重置 | 可重复使用 |
| 计数方向 | 递减计数 | 递增计数 |
| 阻塞对象 | 调用await()的线程 | 所有参与线程 |
| 触发动作 | 无 | 可设置Runnable屏障动作 |
5. 并发编程实战技巧
5.1 线程安全设计模式
- 不可变对象模式:
java复制@Immutable
public final class SafePoint {
private final int x;
private final int y;
public SafePoint(int x, int y) {
this.x = x;
this.y = y;
}
// 只有getter方法...
}
- 线程局部存储(ThreadLocal):
java复制private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDate(Date date) {
return dateFormat.get().format(date); // 每个线程独立实例
}
5.2 性能优化要点
- 上下文切换成本:Linux下约1-5μs,应避免过度创建线程
- 缓存行优化:@Contended注解解决伪共享问题
- 锁消除:JIT对不可能存在竞争的锁进行消除
- 锁粗化:合并连续多次锁请求减少开销
5.3 常见问题排查
死锁检测:
- jstack命令获取线程dump
- 查找"BLOCKED"状态和持有锁信息
- 分析锁依赖环
内存泄漏场景:
- 线程池未正确关闭
- ThreadLocal未及时remove
- 静态集合持有线程引用
最佳实践:
- 优先使用并发集合而非同步包装
- 锁的获取顺序全局统一
- 使用ThreadPoolExecutor而非Executors工厂
- 同步代码块比同步方法更可控
- 考虑使用CompletableFuture进行异步编排
6. Java内存模型(JMM)深度解析
6.1 happens-before规则
保证内存可见性的核心规则:
- 程序顺序规则:线程内操作按程序顺序生效
- 锁规则:解锁操作先于后续加锁操作
- volatile规则:写操作先于后续读操作
- 线程启动规则:Thread.start()先于线程内任何操作
- 传递性规则:A先于B,B先于C → A先于C
6.2 内存屏障类型
| 屏障类型 | 作用 | 对应Java操作 |
|---|---|---|
| LoadLoad | 禁止读-读重排序 | volatile读 |
| StoreStore | 禁止写-写重排序 | volatile写 |
| LoadStore | 禁止读-写重排序 | 锁的获取操作 |
| StoreLoad | 禁止写-读重排序(全能屏障) | volatile写 + 后续读操作 |
6.3 双重检查锁定模式
正确实现方案:
java复制class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) { // 加锁
if (instance == null) { // 第二次检查
instance = new Singleton(); // 初始化
}
}
}
return instance;
}
}
关键点:
- volatile防止指令重排序
- 两次null检查减少锁竞争
- 静态内部类方案是更优选择(利用类加载机制)
7. 现代并发编程趋势
7.1 CompletableFuture组合式异步
函数式编程风格的异步处理:
java复制CompletableFuture.supplyAsync(() -> fetchPrice("AAPL"))
.thenApply(price -> calculateTax(price))
.thenAccept(total -> saveToDB(total))
.exceptionally(ex -> {
log.error("Processing failed", ex);
return null;
});
优势:
- 链式调用避免回调地狱
- 灵活的异常处理机制
- 支持任务组合(allOf/anyOf)
7.2 Virtual Threads(Project Loom)
Java 19引入的轻量级线程:
java复制try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // 自动等待所有任务完成
特性:
- 用户态线程,由JVM调度
- 创建成本极低(约1KB内存)
- 兼容现有Thread API
- 适合高并发I/O密集型场景
7.3 结构化并发(JDK 19+)
通过Scoped Values管理线程生命周期:
java复制try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> user = scope.fork(() -> findUser());
Future<Integer> order = scope.fork(() -> fetchOrder());
scope.join(); // 等待所有子任务
scope.throwIfFailed(); // 统一异常处理
return new Response(user.resultNow(), order.resultNow());
} // 自动取消未完成的任务
设计原则:
- 任务创建与取消具有明确生命周期
- 错误传播与子任务取消自动化
- 观察线程关系如同代码结构
