Java 并发编程是现代后端开发的核心能力之一。本文将系统梳理 Java 并发技术从 synchronized 到 Lock、再到 JUC(java.util.concurrent)工具包,直至 Project Loom 虚拟线程的完整演进路线。每个章节均配有可直接编译运行的完整代码与性能分析思路。
前置知识:本指南面向已掌握 Java 基础语法的开发者,建议预先了解线程创建方式(Thread、Runnable、Callable)与基本同步概念。
Java并发演进(synchronized→Lock→JUC→虚拟线程)
Java 并发模型的演进大致可分为四个阶段:
- JDK 1.0 / 1.2 时代:
synchronized(锁对象) +wait/notify - JDK 1.5(Java 5)时代:引入 JUC 包,
Lock、ConcurrentHashMap、Atomic原子类、ExecutorService等 - JDK 1.7 / 1.8 时代:
ForkJoinPool、CompletableFuture、StampedLock - JDK 19+(Project Loom 预览):虚拟线程(Virtual Threads)、结构化并发(
StructuredTaskScope)、作用域值(ScopedValue)
这种演进并非替代关系,而是层层叠加。在简单场景下 synchronized 依然足够;在高竞争、高灵活性场景中,JUC 工具包提供了更细粒度的控制。虚拟线程则试图用"数百万级轻量线程"颠覆传统线程池模型。
Lock体系与AQS源码(ReentrantLock, ReadWriteLock, StampedLock, Condition)
AQS 核心设计
AbstractQueuedSynchronizer(AQS)是 JUC 中几乎所有同步器(ReentrantLock、CountDownLatch、Semaphore)的底层基石。它维护一个 volatile int state 和一个 FIFO 双向队列(CLH 变种),通过 CAS 操作尝试修改 state,失败则将当前线程封装为 Node 入队并阻塞。
AQS 模板方法设计将"资源获取/释放逻辑"交给子类实现:
tryAcquire(int arg)/tryRelease(int arg):独占模式tryAcquireShared(int arg)/tryReleaseShared(int arg):共享模式
ReentrantLock 示例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 演示 ReentrantLock 的可重入、可中断、超时获取特性
*/
public class ReentrantLockDemo {
// 公平锁:按请求顺序获取,吞吐量略低
private final Lock lock = new ReentrantLock(true);
private int count = 0;
public void increment() {
lock.lock(); // 获取锁
try {
// 可重入:同一线程可再次获取该锁而不会死锁
count++;
System.out.println(Thread.currentThread().getName() + " 计数: " + count);
} finally {
lock.unlock(); // 必须在 finally 中释放,避免异常导致死锁
}
}
// 演示 tryLock 超时获取
public boolean tryIncrementWithTimeout() throws InterruptedException {
if (lock.tryLock(2, java.util.concurrent.TimeUnit.SECONDS)) {
try {
count++;
return true;
} finally {
lock.unlock();
}
}
System.out.println(Thread.currentThread().getName() + " 获取锁超时");
return false;
}
public static void main(String[] args) {
ReentrantLockDemo demo = new ReentrantLockDemo();
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
demo.increment();
}
};
Thread t1 = new Thread(task, "线程-A");
Thread t2 = new Thread(task, "线程-B");
t1.start();
t2.start();
}
}
Lock 与 synchronized 对比
| 特性 | Lock(如 ReentrantLock) | synchronized |
|---|---|---|
| 获取/释放方式 | 显示 lock() / unlock() | JVM 隐式管理(monitorenter/monitorexit) |
| 可重入性 | 支持 | 支持 |
| 公平性 | ReentrantLock(true) 支持公平锁 | 非公平锁 |
| 可中断获取 | lockInterruptibly() 支持 | 不支持 |
| 超时获取 | tryLock(long, TimeUnit) 支持 | 不支持 |
| 条件队列 | 支持多个 Condition | 仅一个 wait/notify 条件集合 |
| 性能 | 高竞争下 CAS + AQS 队列更优 | JDK 6+ 优化后接近,但功能受限 |
| 语法简洁度 | 需写 try-finally,易出错 | 语法简洁,自动释放 |
Condition 精准唤醒
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用 Condition 实现生产者-消费者模式,支持多条件精准唤醒
*/
public class ConditionProducerConsumer {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // 队列未满条件
private final Condition notEmpty = lock.newCondition(); // 队列非空条件
private final int[] buffer = new int[10];
private int count = 0;
private int putIndex = 0;
private int takeIndex = 0;
public void produce(int value) throws InterruptedException {
lock.lock();
try {
// 当缓冲区满时,生产者等待
while (count == buffer.length) {
notFull.await();
}
buffer[putIndex] = value;
putIndex = (putIndex + 1) % buffer.length;
count++;
System.out.println(Thread.currentThread().getName() + " 生产: " + value + ",当前库存: " + count);
// 只唤醒等待消费的线程
notEmpty.signal();
} finally {
lock.unlock();
}
}
public int consume() throws InterruptedException {
lock.lock();
try {
// 当缓冲区空时,消费者等待
while (count == 0) {
notEmpty.await();
}
int value = buffer[takeIndex];
takeIndex = (takeIndex + 1) % buffer.length;
count--;
System.out.println(Thread.currentThread().getName() + " 消费: " + value + ",当前库存: " + count);
// 只唤醒等待生产的线程
notFull.signal();
return value;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ConditionProducerConsumer pc = new ConditionProducerConsumer();
Thread producer = new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
pc.produce(i);
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "生产者");
Thread consumer = new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
pc.consume();
Thread.sleep(150);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "消费者");
producer.start();
consumer.start();
}
}
ReadWriteLock 读写锁
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.HashMap;
import java.util.Map;
/**
* 演示 ReadWriteLock:读读共享,读写互斥,写写互斥
* 适用于读多写少的缓存场景
*/
public class ReadWriteLockCache {
private final ReadWriteLock rwl = new ReentrantReadWriteLock();
private final Map<String, String> cache = new HashMap<>();
public String get(String key) {
// 先加读锁查询缓存
rwl.readLock().lock();
try {
String value = cache.get(key);
if (value != null) {
return value;
}
} finally {
rwl.readLock().unlock();
}
// 缓存未命中,加写锁加载数据
rwl.writeLock().lock();
try {
// 双重检查:可能其他线程已写入
String value = cache.get(key);
if (value == null) {
value = loadFromDatabase(key);
cache.put(key, value);
}
return value;
} finally {
rwl.writeLock().unlock();
}
}
// 模拟从数据库加载
private String loadFromDatabase(String key) {
System.out.println("从数据库加载: " + key);
return "ValueOf" + key;
}
public void put(String key, String value) {
rwl.writeLock().lock();
try {
cache.put(key, value);
} finally {
rwl.writeLock().unlock();
}
}
public static void main(String[] args) {
ReadWriteLockCache cache = new ReadWriteLockCache();
// 启动多个读线程
for (int i = 0; i < 5; i++) {
new Thread(() -> System.out.println("读取: " + cache.get("key1")), "读线程" + i).start();
}
// 启动写线程
new Thread(() -> cache.put("key1", "新值"), "写线程").start();
}
}
StampedLock 乐观读锁
import java.util.concurrent.locks.StampedLock;
/**
* StampedLock 支持三种模式:读锁、写锁、乐观读
* 乐观读不会阻塞写操作,适合读极多、写极少的场景
*/
public class StampedLockDemo {
private final StampedLock lock = new StampedLock();
private double x = 0.0;
private double y = 0.0;
// 乐观读:先读数据,再验证stamp是否被写过
public double distanceFromOrigin() {
long stamp = lock.tryOptimisticRead(); // 获取乐观读戳
double currentX = x;
double currentY = y;
// 验证stamp:若期间有写操作,stamp会变化
if (!lock.validate(stamp)) {
// 被污染了,升级为悲观读锁
stamp = lock.readLock();
try {
currentX = x;
currentY = y;
} finally {
lock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
public void move(double deltaX, double deltaY) {
long stamp = lock.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
lock.unlockWrite(stamp);
}
}
public static void main(String[] args) {
StampedLockDemo point = new StampedLockDemo();
point.move(3, 4);
System.out.println("距离原点: " + point.distanceFromOrigin());
}
}
同步工具类(CountDownLatch/CyclicBarrier/Semaphore/Exchanger/Phaser)
CountDownLatch 等待多线程完成
import java.util.concurrent.CountDownLatch;
/**
* CountDownLatch:一个或多个线程等待其他线程完成操作后再执行
* 倒计时器不可重置,用完后需重新创建实例
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int workerCount = 3;
CountDownLatch latch = new CountDownLatch(workerCount);
for (int i = 0; i < workerCount; i++) {
final int id = i;
new Thread(() -> {
System.out.println("工作者 " + id + " 开始工作...");
try {
Thread.sleep((id + 1) * 1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("工作者 " + id + " 完成工作");
latch.countDown(); // 计数减一
}).start();
}
System.out.println("主线程等待所有工作者完成...");
latch.await(); // 阻塞,直到计数归零
System.out.println("所有工作者已完成,主线程继续执行");
}
}
CyclicBarrier 循环屏障
import java.util.concurrent.CyclicBarrier;
/**
* CyclicBarrier:让一组线程到达屏障后互相等待,直到所有线程都到达后才继续执行
* 与 CountDownLatch 不同:CyclicBarrier 可重置(reset),支持重复使用
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
int party = 3;
// 所有线程到达后执行一次 Runnable(屏障操作)
CyclicBarrier barrier = new CyclicBarrier(party, () -> {
System.out.println("--- 所有线程已到达屏障,本轮汇合完成 ---");
});
Runnable task = () -> {
for (int round = 1; round <= 2; round++) {
try {
System.out.println(Thread.currentThread().getName() + " 正在执行第 " + round + " 轮任务...");
Thread.sleep((int) (Math.random() * 2000));
System.out.println(Thread.currentThread().getName() + " 到达屏障");
barrier.await(); // 到达屏障并等待
} catch (Exception e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < party; i++) {
new Thread(task, "队员-" + i).start();
}
}
}
Semaphore 流量控制
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Semaphore:信号量,控制同时访问某个资源的线程数量
* 常用于限流,如数据库连接池控制
*/
public class SemaphoreDemo {
private final Semaphore semaphore = new Semaphore(3); // 最多允许3个线程同时访问
public void accessResource(int threadId) {
try {
// 获取许可,若当前无可用许可则阻塞
semaphore.acquire();
System.out.println("线程 " + threadId + " 获取许可,开始访问资源");
TimeUnit.SECONDS.sleep(2);
System.out.println("线程 " + threadId + " 释放许可,访问结束");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 释放许可,供其他线程使用
}
}
public static void main(String[] args) {
SemaphoreDemo demo = new SemaphoreDemo();
for (int i = 0; i < 8; i++) {
final int id = i;
new Thread(() -> demo.accessResource(id), "Thread-" + i).start();
}
}
}
Exchanger 线程交换数据
import java.util.concurrent.Exchanger;
/**
* Exchanger:两个线程在Exchange点交换数据
* 典型应用:遗传算法、管道数据校对
*/
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Thread threadA = new Thread(() -> {
try {
String dataA = "来自线程A的数据";
System.out.println("线程A准备交换数据: " + dataA);
// 到达交换点后阻塞,直到线程B也到达
String received = exchanger.exchange(dataA);
System.out.println("线程A收到: " + received);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "线程A");
Thread threadB = new Thread(() -> {
try {
Thread.sleep(2000); // 模拟延迟
String dataB = "来自线程B的数据";
System.out.println("线程B准备交换数据: " + dataB);
String received = exchanger.exchange(dataB);
System.out.println("线程B收到: " + received);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "线程B");
threadA.start();
threadB.start();
}
}
Phaser 动态注册分阶段
import java.util.concurrent.Phaser;
/**
* Phaser:支持动态注册/注销参与者的分阶段同步器
* 比 CountDownLatch 和 CyclicBarrier 更灵活,支持多阶段
*/
public class PhaserDemo {
public static void main(String[] args) {
Phaser phaser = new Phaser(1); // 注册主线程
for (int i = 0; i < 3; i++) {
phaser.register(); // 动态注册参与者
new Thread(new Worker(phaser, i)).start();
}
System.out.println("主线程等待所有参与者完成阶段0...");
phaser.arriveAndAwaitAdvance(); // 主线程到达并等待
System.out.println("阶段0完成,进入阶段1...");
phaser.arriveAndAwaitAdvance();
System.out.println("阶段1完成");
phaser.arriveAndDeregister(); // 主线程注销
}
static class Worker implements Runnable {
private final Phaser phaser;
private final int id;
Worker(Phaser phaser, int id) {
this.phaser = phaser;
this.id = id;
}
@Override
public void run() {
System.out.println("工作者 " + id + " 到达阶段0");
phaser.arriveAndAwaitAdvance(); // 到达阶段0并等待
// 阶段1工作
System.out.println("工作者 " + id + " 执行阶段1任务...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("工作者 " + id + " 到达阶段1");
phaser.arriveAndDeregister(); // 到达阶段1并注销
}
}
}
同步工具对比表
| 工具类 | 核心作用 | 是否可重用 | 典型场景 |
|---|---|---|---|
| CountDownLatch | 等待其他线程完成 | 否(一次性) | 启动服务前等待依赖初始化、多任务并行后汇总 |
| CyclicBarrier | 多线程互相等待汇聚 | 是(循环重置) | 分阶段计算(如 MapReduce 的 map 阶段) |
| Semaphore | 控制并发访问数量 | 是 | 限流、资源池(连接池、线程池) |
| Exchanger | 两线程交换数据 | 是 | 流水线数据校对、遗传算法 |
| Phaser | 多阶段动态注册同步 | 是 | 复杂多阶段任务,需动态增减参与者 |
原子类与CAS(AtomicInteger, LongAdder, ABA问题, AtomicStampedReference)
基本原子类
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* AtomicInteger 基于 CAS(Compare-And-Swap)实现无锁线程安全
* 底层调用 Unsafe.compareAndSwapInt,依赖 CPU 原子指令
*/
public class AtomicDemo {
private final AtomicInteger counter = new AtomicInteger(0);
private final AtomicReference<String> name = new AtomicReference<>("初始值");
public void increment() {
// getAndIncrement 等价于 i++ 的原子版本
int newValue = counter.getAndIncrement();
System.out.println(Thread.currentThread().getName() + " 递增后: " + newValue);
}
// 乐观锁模式:CAS 更新,失败则重试
public void casUpdateName(String expected, String newName) {
boolean updated = name.compareAndSet(expected, newName);
System.out.println("CAS更新" + (updated ? "成功" : "失败") + ",当前值: " + name.get());
}
public static void main(String[] args) {
AtomicDemo demo = new AtomicDemo();
for (int i = 0; i < 5; i++) {
new Thread(demo::increment, "线程-" + i).start();
}
demo.casUpdateName("初始值", "新值A");
demo.casUpdateName("初始值", "新值B"); // 预期已变,CAS失败
}
}
LongAdder 高并发累加
import java.util.concurrent.atomic.LongAdder;
/**
* LongAdder 在高并发下性能优于 AtomicLong
* 原理:分散热点,内部维护 Cell[] 数组,不同线程操作不同 Cell,最后汇总
* Serial(低竞争)场景 AtomicLong 更优,因 LongAdder 有额外汇总开销
*/
public class LongAdderDemo {
private final LongAdder adder = new LongAdder();
public void increment() {
adder.increment(); // 底层无锁,分散竞争
}
public long getSum() {
return adder.sum(); // 获取当前总和
}
public static void main(String[] args) throws InterruptedException {
LongAdderDemo demo = new LongAdderDemo();
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
demo.increment();
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("最终累加结果: " + demo.getSum());
}
}
ABA 问题与 AtomicStampedReference
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* ABA 问题:线程A读取值为A,线程B改为B又改回A,线程A的CAS仍能成功,但值已变化过
* AtomicStampedReference 用版本号(stamp)解决,每次修改 stamp 自增
*/
public class AbaProblemDemo {
public static void main(String[] args) throws InterruptedException {
// 初始值 100,版本号 0
AtomicStampedReference<Integer> value = new AtomicStampedReference<>(100, 0);
int[] stampHolder = new int[1];
Integer current = value.get(stampHolder);
int initialStamp = stampHolder[0];
System.out.println("初始值: " + current + ",stamp: " + initialStamp);
// 线程B:制造 ABA 问题(100→101→100)
Thread threadB = new Thread(() -> {
int[] holder = new int[1];
value.get(holder);
int stamp = holder[0];
// 第一次 CAS:100→101
value.compareAndSet(100, 101, stamp, stamp + 1);
// 获取最新 stamp
value.get(holder);
stamp = holder[0];
// 第二次 CAS:101→100
value.compareAndSet(101, 100, stamp, stamp + 1);
System.out.println("线程B完成 ABA 操纵,当前stamp: " + value.get(holder));
});
threadB.start();
threadB.join();
// 线程A:用旧 stamp 尝试 CAS,会失败
boolean result = value.compareAndSet(100, 200, initialStamp, initialStamp + 1);
System.out.println("线程A用旧stamp CAS结果: " + result); // false,因为 stamp 已变
// 用当前最新 stamp 重试
int[] finalHolder = new int[1];
value.get(finalHolder);
result = value.compareAndSet(100, 200, finalHolder[0], finalHolder[0] + 1);
System.out.println("线程A用新stamp CAS结果: " + result); // true
}
}
Unsafe 模拟 CAS(仅供学习)
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* 使用 Unsafe 理解底层 CAS 实现
* 生产环境不应直接使用 Unsafe,应使用封装好的原子类
*/
public class UnsafeCasDemo {
private volatile long value = 0;
private static final Unsafe UNSAFE;
private static final long VALUE_OFFSET;
static {
try {
// 通过反射获取 Unsafe 实例
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe) field.get(null);
// 获取 value 字段的内存偏移量
VALUE_OFFSET = UNSAFE.objectFieldOffset(UnsafeCasDemo.class.getDeclaredField("value"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public boolean compareAndSwap(long expected, long newValue) {
// 底层调用 CPU cmpxchg 指令
return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expected, newValue);
}
public static void main(String[] args) {
UnsafeCasDemo demo = new UnsafeCasDemo();
boolean result = demo.compareAndSwap(0, 1);
System.out.println("首次 CAS 结果: " + result + ",当前值: " + demo.value);
result = demo.compareAndSwap(0, 2);
System.out.println("二次 CAS 结果: " + result + ",当前值: " + demo.value);
}
}
CompletableFuture异步编排(thenCompose/thenCombine/allOf/anyOf/异常处理)
CompletableFuture vs Future 对比
| 特性 | Future | CompletableFuture |
|---|---|---|
| 获取结果方式 | get() 阻塞 / isDone() 轮询 | get() / join() / 回调驱动(非阻塞) |
| 任务编排 | 不支持链式组合 | 支持 thenApply/thenCompose/thenCombine 等链式 API |
| 异常处理 | get() 抛出 ExecutionException,难以精细处理 | exceptionally/handle 在链中处理异常 |
| 多任务聚合 | 手动管理多个 Future | allOf / anyOf 原生支持 |
| 手动完成 | 不支持 | complete(T) / completeExceptionally(e) 可外部完成 |
| 默认线程池 | 提交时的 Executor | ForkJoinPool.commonPool()(也可自定义 Executor) |
基本异步任务
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* CompletableFuture 入门:创建、完成、异常处理
*/
public class CompletableFutureBasic {
public static void main(String[] args) throws Exception {
// 1. runAsync:无返回值异步任务
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
System.out.println("runAsync 执行线程: " + Thread.currentThread().getName());
});
// 2. supplyAsync:有返回值异步任务
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "Hello";
});
System.out.println("supplyAsync 结果: " + future2.get());
// 3. 手动完成
CompletableFuture<String> future3 = new CompletableFuture<>();
new Thread(() -> future3.complete("手动完成")).start();
System.out.println("手动完成结果: " + future3.get());
}
}
复杂链式编排
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* 演示 thenCompose、thenCombine、thenAccept、thenApply 的区别与组合
*/
public class CompletableFutureChain {
public static void main(String[] args) throws Exception {
// thenApply:转换结果类型 T -> U
CompletableFuture<Integer> lengthFuture = CompletableFuture
.supplyAsync(() -> {
System.out.println("步骤1:获取字符串");
return "CompletableFuture";
})
.thenApply(s -> {
System.out.println("步骤2:计算长度");
return s.length();
});
System.out.println("thenApply 结果: " + lengthFuture.get());
// thenCompose:将结果扁平化为另一个 CompletableFuture(避免 Future<Future<T>>)
CompletableFuture<String> composed = CompletableFuture
.supplyAsync(() -> "user:123")
.thenCompose(userId -> fetchUserName(userId)); // 返回 CompletableFuture<String>
System.out.println("thenCompose 结果: " + composed.get());
// thenCombine:合并两个独立任务的结果
CompletableFuture<Double> combined = fetchPrice("Apple").thenCombine(
fetchExchangeRate(),
(price, rate) -> price * rate // (T, U) -> V
);
System.out.println("thenCombine 结果(美元价格): " + combined.get());
// allOf:等待所有任务完成(无返回值,需自行从各 Future 取结果)
CompletableFuture<String> taskA = asyncTask("A", 1);
CompletableFuture<String> taskB = asyncTask("B", 2);
CompletableFuture<String> taskC = asyncTask("C", 1);
CompletableFuture<Void> allDone = CompletableFuture.allOf(taskA, taskB, taskC);
allDone.thenRun(() -> {
try {
System.out.println("allOf 全部完成: " + taskA.get() + ", " + taskB.get() + ", " + taskC.get());
} catch (Exception e) {
e.printStackTrace();
}
}).get();
// anyOf:任一任务完成即返回
CompletableFuture<Object> anyDone = CompletableFuture.anyOf(
asyncTask("快任务", 1),
asyncTask("慢任务", 3)
);
System.out.println("anyOf 最先完成: " + anyDone.get());
}
static CompletableFuture<String> fetchUserName(String userId) {
return CompletableFuture.supplyAsync(() -> "NameOf" + userId);
}
static CompletableFuture<Double> fetchPrice(String product) {
return CompletableFuture.supplyAsync(() -> 100.0);
}
static CompletableFuture<Double> fetchExchangeRate() {
return CompletableFuture.supplyAsync(() -> 7.2);
}
static CompletableFuture<String> asyncTask(String name, int seconds) {
return CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return name + " 完成";
});
}
}
异常处理模式
import java.util.concurrent.CompletableFuture;
/**
* CompletableFuture 异常处理:exceptionally 与 handle 的区别
* exceptionally:发生异常时提供替代结果,正常情况不执行
* handle:无论是否异常都执行,类似 try-catch-finally 的结合
*/
public class CompletableFutureException {
public static void main(String[] args) {
// 1. exceptionally:仅异常时执行,返回替代值
CompletableFuture<Integer> withExceptionally = CompletableFuture
.supplyAsync(() -> 10 / 0) // 触发 ArithmeticException
.exceptionally(ex -> {
System.out.println("捕获异常: " + ex.getMessage());
return -1; // 提供降级值
});
System.out.println("exceptionally 结果: " + withExceptionally.join());
// 2. handle:无论成败都处理,接收正常结果或异常
CompletableFuture<Integer> withHandle = CompletableFuture
.supplyAsync(() -> 10 / 5)
.handle((result, ex) -> {
if (ex != null) {
System.out.println("handle 捕获异常: " + ex.getMessage());
return -1;
}
return result * 2; // 正常情况继续处理
});
System.out.println("handle 结果: " + withHandle.join());
// 3. 结合自定义线程池与超时控制
java.util.concurrent.Executor executor = java.util.concurrent.Executors.newFixedThreadPool(4);
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "太慢了";
}, executor)
.orTimeout(2, java.util.concurrent.TimeUnit.SECONDS) // JDK 9+ 超时控制
.exceptionally(ex -> "超时或异常: " + ex.getClass().getSimpleName());
System.out.println("超时测试: " + future.join());
((java.util.concurrent.ExecutorService) executor).shutdown();
}
}
ForkJoinPool与工作窃取
ForkJoinPool 基础用法
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
* ForkJoinPool + RecursiveTask:大任务递归拆分为小任务,多线程并行计算
* "工作窃取"机制:线程完成自己队列任务后,从其他线程队列尾部窃取任务执行
* 适用于:递归可分解的计算密集型任务(大数据求和、排序、MapReduce)
*/
public class ForkJoinSum extends RecursiveTask<Long> {
private static final int THRESHOLD = 10000; // 拆分阈值
private final long[] numbers;
private final int start;
private final int end;
public ForkJoinSum(long[] numbers, int start, int end) {
this.numbers = numbers;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
int length = end - start;
if (length <= THRESHOLD) {
// 任务足够小,直接计算
long sum = 0;
for (int i = start; i < end; i++) {
sum += numbers[i];
}
return sum;
}
// 拆分任务
int middle = start + length / 2;
ForkJoinSum leftTask = new ForkJoinSum(numbers, start, middle);
ForkJoinSum rightTask = new ForkJoinSum(numbers, middle, end);
// 异步执行左子任务
leftTask.fork();
// 当前线程直接执行右子任务(减少调度开销)
long rightResult = rightTask.compute();
// 等待左子任务结果并合并
long leftResult = leftTask.join();
return leftResult + rightResult;
}
public static void main(String[] args) {
long[] numbers = new long[100_000_000];
for (int i = 0; i < numbers.length; i++) {
numbers[i] = i + 1;
}
ForkJoinPool pool = new ForkJoinPool();
long startTime = System.currentTimeMillis();
long result = pool.invoke(new ForkJoinSum(numbers, 0, numbers.length));
long endTime = System.currentTimeMillis();
System.out.println("ForkJoin 求和结果: " + result);
System.out.println("耗时: " + (endTime - startTime) + " ms");
pool.shutdown();
}
}
RecursiveAction 示例
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
/**
* RecursiveAction 用于无返回值的 ForkJoin 任务
* 演示:并行初始化大数组
*/
public class ForkJoinInit extends RecursiveAction {
private static final int THRESHOLD = 10000;
private final double[] array;
private final int start;
private final int end;
ForkJoinInit(double[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if (end - start <= THRESHOLD) {
for (int i = start; i < end; i++) {
array[i] = Math.sin(i) * Math.cos(i);
}
} else {
int mid = (start + end) / 2;
ForkJoinInit left = new ForkJoinInit(array, start, mid);
ForkJoinInit right = new ForkJoinInit(array, mid, end);
invokeAll(left, right); // 同时触发两个子任务
}
}
public static void main(String[] args) {
double[] array = new double[10_000_000];
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new ForkJoinInit(array, 0, array.length));
System.out.println("数组初始化完成,样本值: " + array[100]);
pool.shutdown();
}
}
虚拟线程Project Loom(结构化并发, ScopedValue)
虚拟线程基础
import java.util.concurrent.Executors;
import java.time.Duration;
/**
* Project Loom 虚拟线程(JDK 21 正式特性)
* 虚拟线程由 JVM 调度,而非操作系统,创建成本极低(~数百字节)
* 适合 IO 密集型高并发场景,一个平台线程可承载数百万虚拟线程
* 注意:虚拟线程不应与 synchronized 内联(pinning 平台线程)混用,建议使用 ReentrantLock
*/
public class VirtualThreadDemo {
public static void main(String[] args) throws Exception {
// 1. 使用静态工厂创建虚拟线程
Thread vThread = Thread.startVirtualThread(() -> {
System.out.println("虚拟线程运行中: " + Thread.currentThread());
});
vThread.join();
// 2. 使用 ExecutorService 批量创建(try-with-resources 自动关闭)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 100_000; i++) {
final int id = i;
executor.submit(() -> {
// 模拟 IO 操作
try {
Thread.sleep(Duration.ofMillis(10));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
if (id % 20000 == 0) {
System.out.println("任务 " + id + " 完成于 " + Thread.currentThread());
}
});
}
} // 自动等待所有虚拟线程完成
System.out.println("10万个 IO 任务完成");
}
}
结构化并发
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;
/**
* 结构化并发(JDK 21+):子任务生命周期绑定到代码块(try-with-resources)
* 父任务等待所有子任务完成,任一失败可自动取消其他子任务
* 相比 "线程烟花"(fire-and-forget),代码结构更清晰,泄漏更少
*/
public class StructuredConcurrencyDemo {
record User(String name, String address) {}
record Order(String id, double amount) {}
// 模拟获取用户信息(可能耗时的 IO 操作)
private User fetchUser(int userId) throws InterruptedException {
Thread.sleep(200);
return new User("用户" + userId, "北京市");
}
// 模拟获取订单信息
private Order fetchOrder(int userId) throws InterruptedException {
Thread.sleep(300);
return new Order("ORD-" + userId, 199.99);
}
public String getUserSummary(int userId) throws Exception {
// 在 try 块内创建的作用域,所有子任务关联于此
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// fork 两个并行子任务
Subtask<User> userTask = scope.fork(() -> fetchUser(userId));
Subtask<Order> orderTask = scope.fork(() -> fetchOrder(userId));
scope.join(); // 等待所有子任务完成
scope.throwIfFailed(); // 任一任务失败则抛出异常
// 获取结果(此时已确保两个任务都成功完成)
User user = userTask.get();
Order order = orderTask.get();
return user.name + " 的最新订单: " + order.id + ",金额: " + order.amount;
}
}
public static void main(String[] args) throws Exception {
StructuredConcurrencyDemo demo = new StructuredConcurrencyDemo();
String summary = demo.getUserSummary(42);
System.out.println(summary);
}
}
ScopedValue 作用域值
import java.util.concurrent.ScopedValue;
import java.util.concurrent.StructuredTaskScope;
/**
* ScopedValue(JDK 21+预览):线程安全的不可变上下文传递
* 相比 ThreadLocal:
* 1. 不可变性避免隐式修改
* 2. 子任务自动继承父作用域的值(结构化并发中自然传递)
* 3. 作用域结束后自动清理,无内存泄漏风险
*/
public class ScopedValueDemo {
// 声明一个 ScopedValue,类似于 ThreadLocal 但不可变
private static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
private String processRequest() throws Exception {
// 在当前代码块内绑定值,退出代码块后自动解绑
return ScopedValue.where(REQUEST_ID, "REQ-20240901-001").call(() -> {
System.out.println("主任务获取 RequestID: " + REQUEST_ID.get());
return handleSubTasks();
});
}
private String handleSubTasks() throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var task1 = scope.fork(() -> {
// 子任务自动继承父作用域的 ScopedValue
String id = REQUEST_ID.get();
System.out.println("子任务1 RequestID: " + id);
return "子结果A";
});
var task2 = scope.fork(() -> {
String id = REQUEST_ID.get();
System.out.println("子任务2 RequestID: " + id);
return "子结果B";
});
scope.join();
return task1.get() + " + " + task2.get();
}
}
public static void main(String[] args) throws Exception {
ScopedValueDemo demo = new ScopedValueDemo();
String result = demo.processRequest();
System.out.println("最终结果: " + result);
}
}
线程池ThreadPoolExecutor详解
import java.util.concurrent.*;
/**
* ThreadPoolExecutor 七大参数深度解析:
* 1. corePoolSize:核心线程数,即使空闲也保留(除非 allowCoreThreadTimeOut)
* 2. maximumPoolSize:最大线程数
* 3. keepAliveTime:非核心线程空闲存活时间
* 4. unit:keepAliveTime 的时间单位
* 5. workQueue:任务等待队列(ArrayBlockingQueue / LinkedBlockingQueue / SynchronousQueue)
* 6. threadFactory:创建线程的工厂(可设置线程名称、守护状态)
* 7. handler:拒绝策略(AbortPolicy / CallerRunsPolicy / DiscardPolicy / DiscardOldestPolicy)
*/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
// 自定义线程池(阿里规范推荐,避免 Executors 的隐藏风险)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60L, // 非核心线程存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10), // 容量为10的阻塞队列
new CustomThreadFactory(), // 自定义线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:让调用者线程执行
);
// 允许核心线程在空闲时也被回收(默认不回收)
executor.allowCoreThreadTimeOut(true);
for (int i = 0; i < 20; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务 " + taskId);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 优雅关闭:先停止接受新任务,再等待已提交任务完成
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 超时则强制中断
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
static class CustomThreadFactory implements ThreadFactory {
private int counter = 0;
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "自定义线程-" + (++counter));
t.setUncaughtExceptionHandler((thread, ex) -> {
System.out.println("线程 " + thread.getName() + " 发生未捕获异常: " + ex.getMessage());
});
return t;
}
}
}
线程池工作原理解析
当提交一个新任务时,线程池按以下逻辑处理:
- 当前运行线程数 < corePoolSize:创建新核心线程执行任务,即使有空闲线程
- 当前运行线程数 >= corePoolSize:任务进入
workQueue等待 - workQueue 已满且运行线程数 < maximumPoolSize:创建临时(非核心)线程执行任务
- 运行线程数 >= maximumPoolSize 且队列已满:触发拒绝策略
最佳实践:IO 密集型任务设置较大核心线程数(如 2*N cpu),计算密集型设置接近 N cpu;队列容量不宜无限(如 LinkedBlockingQueue 无界队列易导致 OOM)。
Java内存模型(happens-before, volatile, final语义)
volatile 保证可见性与有序性
import java.util.concurrent.TimeUnit;
/**
* volatile 关键字的两个语义:
* 1. 可见性:写 volatile 变量会立即刷新到主内存,读会从主内存刷新
* 2. 有序性:禁止指令重排序(内存屏障 insert)
* 注意:volatile 不保证原子性,复合操作(如 i++)仍需 synchronized 或原子类
*/
public class VolatileDemo {
// 使用 volatile 保证状态变更对所有线程立即可见
private volatile boolean running = true;
private volatile int count = 0;
public void stop() {
running = false; // 写 volatile,随后插入 StoreLoad 屏障
}
public void increment() {
// 以下不是原子操作,并发下可能丢失更新
count++; // 分解为:读 -> 修改 -> 写,三步骤
}
public static void main(String[] args) throws Exception {
VolatileDemo demo = new VolatileDemo();
Thread worker = new Thread(() -> {
while (demo.running) {
// 若 running 非 volatile,此循环可能永远看不到主线程的修改
}
System.out.println("工作者线程检测到 stop 信号,退出循环");
});
worker.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("主线程发送 stop 信号");
demo.stop();
worker.join();
}
}
happens-before 规则
Java 内存模型(JMM)定义了 happens-before 关系,确保程序员无需深入理解硬件重排序即可获得可靠的多线程语义。核心规则包括:
- 程序次序规则:同一线程中,书写在前面的操作 happens-before 后面的操作
- 监视器锁规则:
unlockhappens-before 后面对同一把锁的lock - volatile 变量规则:
volatile写 happens-before 后面对该变量的读 - 线程启动规则:
Thread.start()happens-before 线程中所有操作 - 线程终止规则:线程中所有操作 happens-before 线程终止检测(
join()返回、isAlive()为 false) - 中断规则:
interrupt()happens-before 检测到中断(isInterrupted()) - 对象终结规则:构造函数执行 happens-before
finalize() - 传递性:若 A happens-before B,且 B happens-before C,则 A happens-before C
final 安全发布
/**
* final 字段的内存语义:构造函数中对 final 字段的写入,
* happens-before 后续每个线程对同一个对象 final 字段的读取。
* 这意味着 final 字段值一旦构造完成便不可变,且立即可见于其他线程。
* 注意:不要在构造函数中将 this 引用逸出(escape),否则可能破坏 final 语义。
*/
public class FinalSafePublication {
private final int value;
private final String name;
public FinalSafePublication(int value, String name) {
this.value = value;
this.name = name;
// 错误示例:不要在构造函数内启动线程或注册监听器并传递 this
// new Thread(() -> System.out.println(this.name)).start(); // this 逸出!
}
public int getValue() {
return value; // 其他线程读到的必然是构造完成后的值
}
public String getName() {
return name;
}
}
并发最佳实践与死锁排查
死锁示例与预防
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 死锁产生的四个必要条件:互斥、请求与保持、不剥夺、循环等待
* 预防策略:
* 1. 按固定顺序获取多把锁
* 2. 使用 tryLock 超时获取,失败则释放已持有锁
* 3. 尽量减少锁粒度与持有时间
*/
public class DeadlockPrevention {
private final Lock lockA = new ReentrantLock();
private final Lock lockB = new ReentrantLock();
// 错误示例:循环等待导致死锁
public void deadlockScenario() {
Thread t1 = new Thread(() -> {
lockA.lock();
try {
System.out.println("线程1获取锁A");
TimeUnit.MILLISECONDS.sleep(100);
lockB.lock();
try {
System.out.println("线程1获取锁B");
} finally {
lockB.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lockA.unlock();
}
}, "死锁线程1");
Thread t2 = new Thread(() -> {
lockB.lock(); // 若与 t1 同时获取不同顺序,可能死锁
try {
System.out.println("线程2获取锁B");
TimeUnit.MILLISECONDS.sleep(100);
lockA.lock();
try {
System.out.println("线程2获取锁A");
} finally {
lockA.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lockB.unlock();
}
}, "死锁线程2");
// 实际运行可能触发死锁,此处仅演示
// t1.start(); t2.start();
}
// 正确示例:固定顺序获取 + tryLock 超时回退
public void safeOrdering() {
// 按 hashCode 排序,所有线程按相同顺序获取
Lock first = lockA.hashCode() < lockB.hashCode() ? lockA : lockB;
Lock second = lockA.hashCode() < lockB.hashCode() ? lockB : lockA;
try {
if (first.tryLock(1, TimeUnit.SECONDS)) {
try {
if (second.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println("成功按序获取两把锁");
} finally {
second.unlock();
}
}
} finally {
first.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
DeadlockPrevention demo = new DeadlockPrevention();
demo.safeOrdering();
}
}
死锁排查命令
当线上出现疑似死锁时,可使用以下 JDK 工具进行诊断:
# 1. 打印所有线程的堆栈与锁信息,自动检测死锁
jstack -l <pid> > thread_dump.txt
# 2. 在 jstack 输出中搜索 "Found one Java-level deadlock:" 关键字
# 3. 使用 jconsole 或 VisualVM 的 "线程" 面板,查看 "检测死锁" 按钮
# 4. Java Flight Recorder (JFR) 可录制锁竞争事件( jdk.LockContention )
并发编程最佳实践清单
- 优先使用高层并发工具:
ConcurrentHashMap优于Collections.synchronizedMap,LongAdder优于AtomicLong(高竞争),CompletableFuture优于手动管理Thread - 避免过度同步:只在必要处加锁,锁粒度尽量小;读写分离场景用
ReadWriteLock或StampedLock - 慎用 ThreadLocal:在虚拟线程与线程池环境下容易内存泄漏,考虑
ScopedValue(JDK 21+)替代 - 线程安全文档化:公共类应在 Javadoc 中明确标注线程安全策略(不可变、线程安全、有条件线程安全、线程兼容、线程对立)
- 超时与取消:长时间阻塞的操作应支持超时(
Future.get(timeout)、tryLock(long, TimeUnit))与中断响应 - 测试并发代码:使用
jcstress(Java Concurrency Stress)工具验证 JMM 假设,而非仅靠单元测试
常见问题(FAQ)
Q1:synchronized 和 ReentrantLock 应该如何选择?
在以下情况优先使用 ReentrantLock:需要尝试非阻塞获取(tryLock)、可中断获取(lockInterruptibly)、可超时获取、需要多个条件队列(Condition)、需要公平锁。否则,对于简单同步块,synchronized 语法更简洁且由 JVM 自动优化,出错概率更低。JDK 19+ 的虚拟线程环境中,长时间的 synchronized 块可能"钉住"(pin)平台线程,建议改用 ReentrantLock。
Q2:为什么高并发下 LongAdder 比 AtomicLong 更快?
AtomicLong 所有线程竞争同一变量,大量 CAS 失败后自旋,导致缓存行乒乓(cache line bouncing)。LongAdder 内部维护一个 Cell[] 数组,线程根据 hash 映射到不同 Cell 上进行累加,降低冲突概率。最后调用 sum() 时汇总所有 Cell。这是一种空间换时间、分散热点的设计。
Q3:CompletableFuture 的 thenApply 与 thenCompose 有什么区别?
thenApply 接收一个 Function<T, U>,将当前结果转换为另一种类型,返回 CompletableFuture<U>。thenCompose 接收一个 Function<T, CompletableFuture<U>>,用于将当前结果扁平化为另一个异步操作的结果,避免 CompletableFuture<CompletableFuture<U>> 的嵌套。当后续操作本身也是异步 API 时,使用 thenCompose。
Q4:什么是虚拟线程的 “pinning” 问题?如何规避?
当虚拟线程执行 synchronized 代码块或调用本地方法(JNI)时,它会被"固定"到底层的平台线程上。如果此时虚拟线程执行的是阻塞 IO,平台线程也会被阻塞,无法去调度其他虚拟线程,从而浪费平台线程资源。规避方法:在虚拟线程中避免使用 synchronized,改用 ReentrantLock;避免在虚拟线程中执行长时间阻塞的 JNI 调用。
Q5:线程池提交任务后,如何优雅地关闭并确保所有任务都已执行完毕?
调用 shutdown() 发起优雅关闭(不再接受新任务,但等待已提交任务完成),然后调用 awaitTermination(timeout, unit) 等待一段时间。如果超时仍有未完成任务,根据业务需求选择继续等待或调用 shutdownNow() 强制中断正在执行的任务。注意 shutdownNow() 返回的任务列表是未被执行的任务,已启动但被中断的任务需要自行处理中断异常。
总结
本文从 synchronized 出发,系统梳理了 Java 并发编程的核心技术栈:
- Lock 与 AQS:理解可重入锁、读写锁、乐观读的实现差异与适用场景
- 同步工具类:根据"一次性等待"、“循环汇聚”、“流量控制”、“数据交换”、“多阶段任务"选择合适工具
- 原子类与 CAS:掌握无锁编程思想,理解 ABA 问题与
LongAdder的优化原理 - CompletableFuture:用声明式链式 API 替代回调地狱,优雅处理多任务编排与异常
- ForkJoinPool:利用工作窃取模型加速可分解的计算密集型任务
- Project Loom:虚拟线程与结构化并发正在重塑 Java 高并发 IO 编程范式
- 线程池与 JMM:正确配置线程池参数,理解 happens-before 规则,编写真正线程安全的代码
并发编程没有银弹。每一种锁、每一个工具类都有其特定上下文与权衡。在实际工程中,建议优先使用 JUC 提供的高层抽象,仅在性能关键路径上深入底层优化。配合 jstack、JFR、jcstress 等工具进行监控与验证,方能在高并发场景下行稳致远。
参考文档:OpenJDK 21 Documentation、《Java并发编程实战》(Goetz 等著)、JEP 444(Virtual Threads)、JEP 453(Structured Concurrency)
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。