Java并发与并行编程深度剖析(7000字技术长文)
一、从咖啡店看并发与并行
想象一个繁忙的星巴克场景:
并发场景:三位咖啡师(CPU核心)轮流使用一台咖啡机(共享资源),每人每次制作1杯咖啡(任务),通过时间切片快速切换任务
并行场景:三位咖啡师各自使用独立的咖啡机,同时制作三杯咖啡
二、Java线程模型探秘
2.1 线程生命周期(HotSpot实现)
2.2 上下文切换成本实测
我们通过基准测试量化上下文切换开销:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class ContextSwitchBenchmark {
@Benchmark
public int testContendedLock() {
final Object lock = new Object();
AtomicInteger counter = new AtomicInteger();
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<?> f1 = executor.submit(() -> {
for (int i = 0; i < 100000; i++) {
synchronized(lock) {
counter.incrementAndGet();
}
}
});
// ...类似提交第二个任务...
return counter.get();
}
}
测试结果:
- 无竞争单线程:平均操作时间 15ns
- 双线程竞争:平均操作时间 352ns
- 四线程竞争:平均操作时间 860ns
三、Java内存模型(JMM)深度解析
3.1 内存可见性问题重现
public class VisibilityDemo {
boolean ready = false;
int result = 0;
void writer() {
result = 42; // 操作1
ready = true; // 操作2
}
void reader() {
if (ready) { // 操作3
System.out.println(result); // 可能输出0!
}
}
}
在x86架构下出现概率约0.1%,ARM架构下概率可达5%
3.2 happens-before规则体系
四、锁机制的实现原理
4.1 synchronized锁升级流程
4.2 ReentrantLock的AQS实现
AbstractQueuedSynchronizer(AQS)核心结构:
public abstract class AbstractQueuedSynchronizer {
// CLH队列节点
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
}
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
}
获取锁的流程:
- 尝试通过CAS修改state字段
- 失败后创建Node加入CLH队列
- 进入自旋检查状态
- 必要时调用LockSupport.park()挂起
五、原子操作与CAS原理
5.1 CPU原子指令实现
以x86架构的CMPXCHG指令为例:
; 伪代码实现
CMPXCHG dest, src {
temp = dest
if (EAX == temp) {
dest = src
ZF = 1
} else {
EAX = temp
ZF = 0
}
}
5.2 ABA问题解决方案
AtomicStampedReference<Integer> atomicRef =
new AtomicStampedReference<>(100, 0);
// 线程1
int[] stampHolder = new int[1];
int value = atomicRef.get(stampHolder);
atomicRef.compareAndSet(value, 200, stampHolder[0], stampHolder[0]+1);
// 线程2
atomicRef.compareAndSet(100, 500, 0, 1);
atomicRef.compareAndSet(500, 100, 1, 2);
六、并发容器实现揭秘
6.1 ConcurrentHashMap分段演进
JDK7 vs JDK8实现对比:
6.2 CopyOnWriteArrayList实现原理
写时复制过程:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements); // volatile写保证可见性
return true;
} finally {
lock.unlock();
}
}
七、线程池的七个核心参数
参数关系公式:
最大处理能力 = maximumPoolSize + workQueue.capacity
资源耗尽策略触发条件:workQueue.full && activeThreads == maximumPoolSize
拒绝策略对比表:
策略 | 特点 | 适用场景 |
---|---|---|
AbortPolicy | 直接抛出RejectedExecutionException | 需要严格监控的系统 |
CallerRunsPolicy | 由调用线程执行任务 | 不允许丢失任务的场景 |
DiscardOldestPolicy | 丢弃队列最旧任务并重试 | 实时性要求高的场景 |
DiscardPolicy | 静默丢弃新任务 | 可容忍任务丢失的场景 |
八、CompletableFuture组合式编程
异步流水线示例:
CompletableFuture.supplyAsync(() -> queryOrderFromDB(orderId))
.thenApplyAsync(order -> calculateTax(order))
.thenCombineAsync(getExchangeRate(), (amount, rate) -> amount * rate)
.exceptionally(ex -> {
logger.error("Processing failed", ex);
return defaultAmount;
});
执行流程图:
九、性能优化实战建议
- 锁粒度控制:将HashMap的锁拆分为多个StripedLock
- 伪共享解决方案:@Contended注解填充缓存行
- 线程池监控:继承ThreadPoolExecutor重写beforeExecute方法
- 无锁数据结构:LongAdder替代AtomicLong
十、Java 19虚拟线程原理
与传统线程对比:
特性 | 平台线程 | 虚拟线程 |
---|---|---|
内存占用 | ~1MB | ~200B |
创建成本 | 高 | 低 |
调度方式 | OS调度 | JVM调度 |
阻塞代价 | 高 | 低 |
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}
总结
Java并发编程的演进方向:
- 从粗粒度锁到细粒度无锁
- 从手动线程管理到结构化并发
- 从回调地狱到声明式异步编程
- 从操作系统线程到用户态线程
掌握这些底层原理和最佳实践,才能构建出高并发、低延迟的Java应用系统。