当前位置: 首页 > news >正文

Java 并发编程总结

最近系统梳理了 Java 并发编程的核心知识点,将一些关键内容整理如下,方便日后回顾。

1. JMM 与 Happens-Before 规则理解

JMM(Java 内存模型)定义了线程如何通过内存进行交互,以及何时能够看到其他线程的写入操作。它主要解决了可见性与有序性问题(注意:原子性不在其范畴内)。

Happens-Before 是 JMM 的核心概念,常见的规则包括:

  1. 程序次序规则:单线程内顺序执行
  2. 监视器锁规则:解锁操作先于后续的加锁操作
  3. volatile 变量规则:写操作先于后续的读操作
  4. 线程启动与终止规则:start() 先于线程内操作,线程内操作先于终止检测
  5. 传递性规则:A → B,B → C,则 A → C
  6. 中断规则:对线程的 interrupt() 调用先于被中断线程的响应
  7. final 字段规则:正确构造的 final 字段在构造完成后对其他线程可见

指令重排序是允许的,但前提是不能破坏 Happens-Before 约束。JIT 编译器的重排序受到内存屏障的限制,synchronized 和 volatile 在编译后都会生成相应的屏障指令。

// 典型示例:volatile 保证可见性
volatile boolean ready = false;
int data = 0;void writer() {data = 42;          // 普通写ready = true;       // volatile 写,建立HB关系
}void reader() {if (ready) {        // volatile 读,看到true则之前的写操作都可见System.out.println(data); // 保证输出42}
}

易错点:
• 误以为 volatile 能保证复合操作的原子性(如 i++)
• 不了解 final 字段的发布规则导致对象逸出

延伸思考:为什么 final 字段构造完成后对其他线程可见?
参考答案:因为 JMM 在构造结束时插入了内存屏障,且要求对象引用在构造完成前不能逸出。

2. volatile 与 synchronized 的区别与适用场景

这两个关键字解决了不同层面的并发问题:
volatile:
• 保证可见性和禁止指令重排序
• 不保证原子性
• 适用场景:状态标志位、一次性发布(如配置加载)

synchronized:
• 保证互斥访问和可见性
• 通过进入/退出临界区时的内存屏障实现
• 适用场景:需要原子性操作的复合逻辑

// volatile 典型用法:停止标志
class StoppableTask {private volatile boolean stopRequested;public void run() {while (!stopRequested) {// 执行任务}}public void stop() {stopRequested = true;}
}

易错点:使用 volatile 进行计数操作(如 ++)无法保证原子性

延伸思考:AtomicInteger 如何实现原子性?
答:AtomicInteger 通过 CAS + 自旋循环实现原子性,适合计数器场景。

3. CAS 原理与 ABA 问题解决方案

CAS(Compare-And-Swap)是无锁编程的核心操作:
• 比较内存中的值与期望值,相等则写入新值
• 基于硬件指令实现,冲突时通过自旋重试
• JDK9+ 使用 VarHandle 替代 Unsafe

ABA 问题:值从 A → B → A 的变化过程,CAS 无法感知中间状态变化
• 解决方案:添加版本号(AtomicStampedReference)或使用带标记的指针

// 使用版本号解决ABA问题
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);int[] stampHolder = new int[1];
int currentStamp = ref.getStamp();
int currentValue = ref.get(stampHolder);// 更新时同时检查值和版本号
ref.compareAndSet(currentValue, 200, currentStamp, currentStamp + 1);

注意:高竞争场景下自旋可能导致 CPU 占用过高,需考虑退避策略

4. synchronized 与 ReentrantLock 选择策略

两者都是可重入互斥锁,但各有特点:
synchronized:
• 语法简洁,JVM 内置支持
• JIT 编译器会进行偏向锁、轻量级锁等优化
• 自动释放锁,不易遗漏

ReentrantLock:基于 AQS(state + CLH 队列)
• 支持公平锁选项
• 支持可中断的锁获取
• 支持超时尝试获取锁
• 支持多个条件变量(Condition)

// ReentrantLock 的典型用法
ReentrantLock lock = new ReentrantLock(true); // 公平锁try {if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {try {// 临界区操作} finally {lock.unlock(); // 必须手动释放}}
} catch (InterruptedException e) {Thread.currentThread().interrupt();
}

5. AQS 核心设计思想

AQS(AbstractQueuedSynchronizer)是 Java 并发包的核心基础框架:

核心组件:
• state:同步状态,不同子类有不同语义(如重入次数、许可数等)
• CLH 队列:双向队列管理等待线程
• 两种模式:独占模式(ReentrantLock)和共享模式(Semaphore)
条件变量:ConditionObject 维护单独的条件队列,signal 时转移到同步队列

工作流程:

  1. 尝试获取锁(tryAcquire)
  2. 失败则加入队列,park 等待
  3. 释放锁时唤醒后继节点

注意:自定义 AQS 时需要正确处理可重入和中断响应
共享模式的典型实现?(Semaphore、CountDownLatch)

6. Condition 与 wait/notify 对比

Condition 提供了比传统 wait/notify 更精细的线程协调机制:

优势:
• 一个锁可以关联多个条件队列
• 支持精确唤醒(signal vs signalAll)
• 更清晰的语义表达(wait/notify 依赖对象监视器,只有一个等待集)

// 生产者-消费者模式中使用两个Condition
class BoundedBuffer {final Lock lock = new ReentrantLock();final Condition notFull = lock.newCondition();final Condition notEmpty = lock.newCondition();final Object[] items = new Object[100];int putptr, takeptr, count;public void put(Object x) throws InterruptedException {lock.lock();try {while (count == items.length)notFull.await();items[putptr] = x;if (++putptr == items.length) putptr = 0;++count;notEmpty.signal();} finally {lock.unlock();}}// take方法类似
}

注意:必须在持有锁时调用 await/signal,且 signal 可能早于 await 导致信号丢失。
为什么必须在 try/finally 中 unlock()?(异常路径也要释放)

7. 读写锁选择:ReadWriteLock vs StampedLock

根据读写模式选择不同的锁实现:

ReentrantReadWriteLock:
• 读锁共享,写锁独占
• 可重入,支持条件变量
• 写锁可降级为读锁

StampedLock:
• 提供乐观读模式,性能更高
• 不可重入,不支持条件变量
• 通过 stamp 验证锁有效性

// StampedLock 乐观读示例
StampedLock lock = new StampedLock();
int value = 0;int readValue() {long stamp = lock.tryOptimisticRead(); // 乐观读int currentValue = value;if (!lock.validate(stamp)) {          // 验证期间是否有写操作stamp = lock.readLock();          // 转为悲观读try {currentValue = value;} finally {lock.unlockRead(stamp);}}return currentValue;
}

选型建议

  • 读多写少且性能要求高 → StampedLock;
  • 需要可重入或条件变量 → ReadWriteLock

8. 线程池参数配置实践

线程池 ThreadPoolExecutor 配置需要根据业务特点设计:

核心参数:
• corePoolSize:核心线程数,CPU 密集型建议 Ncpu+1
• maximumPoolSize:最大线程数,I/O 密集型可适当增大
• workQueue:任务队列,推荐有界队列避免 OOM
• keepAliveTime+unit:空闲线程存活时间
• threadFactory:线程工厂,建议设置线程名称和异常处理器
• handler:拒绝策略,根据业务需求选择(Abort(默认)、CallerRuns、Discard、DiscardOldest)

// 线程池配置示例
ThreadPoolExecutor executor = new ThreadPoolExecutor(4,                                      // corePoolSize16,                                     // maximumPoolSize  60, TimeUnit.SECONDS,                   // keepAliveTimenew LinkedBlockingQueue<>(1000),        // 有界队列r -> {                                  // 线程工厂Thread t = new Thread(r, "biz-thread-" + counter.getAndIncrement());t.setUncaughtExceptionHandler((thread, ex) -> logger.error("Uncaught exception in {}", thread.getName(), ex));return t;},new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

监控指标:活跃线程数、队列大小、拒绝任务数等需要纳入监控。
任务提交速率远超处理能力怎么办?(背压/限流/降级/拆分池/熔断)

9. CompletableFuture 组合编程

CompletableFuture 提供了强大的异步编程能力:

常用操作:

  • supplyAsync(task, executor): 指定线程池,默认是 ForkJoinPool.commonPool
    • thenApply/thenAccept:转换和消费结果
    • thenCompose:扁平化嵌套 Future
    • thenCombine:合并多个 Future 结果
    • allOf/anyOf:等待所有/任意任务完成

异常处理:
• exceptionally:异常恢复
• handle:统一处理结果和异常
• whenComplete:完成时回调

// 并行调用多个服务并合并结果
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(this::callServiceA, executor);
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(this::callServiceB, executor);CompletableFuture<String> combined = futureA.thenCombine(futureB, (a, b) -> a + b).exceptionally(ex -> {logger.warn("Service call failed, using fallback", ex);return "fallback-value";});String result = combined.join();

与 Future 的区别?(可组合、非阻塞回调)

10. ForkJoinPool 工作窃取机制

ForkJoinPool 采用工作窃取(work-stealing)算法提升性能:

核心机制:
• 每个工作线程维护自己的双端队列
• 本地任务 LIFO 执行,窃取其他队列任务时从尾部获取
• 减少竞争,提高 CPU 利用率

并行流注意事项:
• 并行流底层使用 ForkJoinPool.commonPool
• 避免在并行流中执行阻塞 I/O 操作
• 注意 ThreadLocal 上下文传递问题

// 在自定义ForkJoinPool中执行并行流
ForkJoinPool customPool = new ForkJoinPool(4);
try {customPool.submit(() -> dataList.parallelStream().map(this::processItem).collect(Collectors.toList())).get();
} finally {customPool.shutdown();
}

Java 并发工具实践总结(续)

11. 同步器选择:CountDownLatch vs CyclicBarrier vs Phaser

在实际项目中,根据不同的同步需求选择合适的同步工具非常重要:

CountDownLatch - 一次性门闩
• 典型场景:主线程等待多个子任务完成初始化
• 特点:计数不可重置,等待线程在计数归零后继续执行

// 等待三个服务初始化完成
CountDownLatch latch = new CountDownLatch(3);executor.execute(() -> {initServiceA();latch.countDown();
});executor.execute(() -> {initServiceB(); latch.countDown();
});executor.execute(() -> {initServiceC();latch.countDown();
});// 主线程等待所有服务初始化完成
latch.await();
logger.info("所有服务初始化完成");

CyclicBarrier - 可复用栅栏
• 典型场景:多线程数据分片处理,需要同步点
• 特点:可重置,支持栅栏动作(barrier action)

Phaser - 多阶段栅栏
• 典型场景:多阶段任务协作,参与者动态变化
• 优势:支持阶段推进、动态注册/注销参与者

未处理屏障破裂异常(BrokenBarrierException)
异常路径忘记调用 countDown(),导致主线程永久等待
Phaser 相比 CyclicBarrier 的优势?(动态参与者、阶段控制)

12. BlockingQueue 的几种实现差异

不同的 BlockingQueue 实现适用于不同场景:

  • ArrayBlockingQueue:基于数组的有界队列,支持公平策略(减少线程饥饿),固定容量,内存占用可控。

  • LinkedBlockingQueue:基于链表的队列,默认无界(Integer.MAX_VALUE),吞吐量通常更高,但可能导致 OOM。适合任务量相对可控的场景

  • PriorityBlockingQueue:无界优先级队列,消费顺序取决于优先级,非入队顺序,适合需要优先处理某些任务的场景

  • DelayQueue:基于优先级队列的延迟队列,元素需实现 Delayed 接口,适合定时任务、缓存过期等场景

注意:生产禁用无界队列,避免无界队列导致的内存溢出和背压失效问题。

13. ThreadLocal 的正确使用与内存泄漏防范

ThreadLocal 非常适合存储线程上下文信息,但需要谨慎使用。

// 线程安全的日期格式化
private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTER = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));public String formatDate(Date date) {try {return DATE_FORMATTER.get().format(date);} finally {// 关键:在线程池环境中必须清理,避免内存泄漏DATE_FORMATTER.remove();}
}

内存泄漏风险:
• 线程池中的线程会复用,如果不调用 remove(),ThreadLocal 值会一直存在
• 值对象如果较大,会导致严重的内存泄漏
• 建议使用 try-finally 确保清理

InheritableThreadLocal在线程池下为何危险 :在线程池中,子任务可能由不同线程执行,继承语义会错乱,不建议在异步场景使用。

14. 死锁预防与诊断实践

死锁是并发编程中的经典问题,需要从预防和诊断两方面着手:

预防措施:

// 使用 tryLock 避免死锁
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();public void safeOperation() {if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {try {if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {try {// 临界区操作doCriticalWork();} finally {lock2.unlock();}}} finally {lock1.unlock();}}
}

统一加锁顺序是预防死锁的有效方法,确保所有线程以相同顺序获取锁。

诊断工具
• jstack:查看线程状态和锁持有情况
• JFR:Java Flight Recorder 记录详细并发事件
• ThreadMXBean.findDeadlockedThreads():编程式检测死锁

概念区分:
• 死锁:相互等待对方释放锁
• 活锁:线程不断重试但无法前进(如消息处理失败不断重试)
• 饥饿:某些线程长期得不到执行机会

15. ConcurrentHashMap 内部机制理解

核心改进
• 摒弃分段锁,使用 bin-level 锁(synchronized)+ CAS 初始化桶。
• 高冲突时链表转红黑树(treeify),阈值 8/6,提升查询效率
• 扩容时支持多线程协同迁移

使用注意:
• size() 返回的是近似值,因为并发环境下精确计数代价太高
• 复合操作(如 putIfAbsent)仍需外部同步保证原子性
• computeIfAbsent 在并发环境下可能被多次执行,需要保证幂等性

16. 结构化并发改善代码可维护性

结构化并发让异步代码具有同步代码的清晰结构。

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {// 并行发起多个RPC调用Supplier<String> userTask = scope.fork(() -> userService.getUser(userId));Supplier<String> orderTask = scope.falk(() -> orderService.getOrders(userId));// 等待所有任务完成或任一失败scope.join();scope.throwIfFailed();// 组合结果return new UserResponse(userTask.get(), orderTask.get());
}

优势:
• 任务生命周期管理自动化
• 异常传播和取消机制更完善
• 避免"悬挂任务"问题

  • 与 CompletableFuture.allOf 相比,结构化并发提供了更好的可观察性和控制力。

17. 性能优化:减少竞争与伪共享

在高并发场景下,减少竞争是关键优化方向:

减少锁竞争:
• 数据分片(如 LongAdder 的分段计数)
• 读写分离(读写锁、CopyOnWrite)
• 乐观策略(StampedLock 乐观读)

解决伪共享:

// 使用缓存行填充避免伪共享
@jdk.internal.vm.annotation.Contended
public class Counter {private volatile long value;// 自动添加填充字节,避免与其他热点字段共享缓存行
}

LongAdder 的热点规避原理:通过分桶(Cell数组)分散写压力,sum时合并结果,在高并发写场景下性能远超AtomicLong。

http://www.dtcms.com/a/350678.html

相关文章:

  • SCSS上传图片占位区域样式
  • 基于多通道同步分析的智能听诊系统应用程序
  • 动态住宅代理:跨境电商数据抓取的稳定解决方案
  • vue-admin-template vue-cli 4升5(vue2版)
  • C语言中哪些常见的坑
  • Linux的奇妙冒险———进程信号
  • 滲透測試工具
  • Microsoft 365 中的 Rules-Based Classification 功能深度解析:企业数据治理与合规的智能基石
  • 25年8月通信基础知识补充2:星座的峭度(Kurtosis)、ISAC
  • 朴素贝叶斯分类器
  • A股市场高级日历效应详解与实战指南
  • 【P2P】P2P主要技术及RELAY服务1:python实现
  • 【Git】fatal: Unable to create ‘.git/index.lock’: File exists.
  • 迁移面试题
  • 亚远景- 从算法到刹车片:ISO/PAS 8800如何量化自动驾驶的“安全冗余”?
  • Life:Internship in OnSea Day 64
  • PyTorch损失函数全解析与实战指南
  • 高性能C++实践:原子操作与无锁队列实现
  • C++ #pragma
  • C++初阶(3)C++入门基础2
  • 现代C++工具链实战:CMake + Conan + vcpkg依赖管理
  • MYSQL的bin log是什么
  • JUC并发编程08 - 同步模式/异步模式
  • ROS2 python功能包launch,config文件编译后找不到
  • 链表OJ习题(2)
  • 搭建基于LangChain实现复杂RAG聊天机器人
  • AI在软件研发流程中的提效案例
  • 在vue3后台项目中使用热力图,并给热力图增加点击选中事件
  • Java中删除字符串首字符
  • 【51单片机】【protues仿真】基于51单片机数码管温度报警器系统