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

并发编程、锁、线程池知识整理<1>

前言

  1. synchronized 底层原理?锁升级过程(偏向→轻量→重量)?
  2. ReentrantLock vs synchronized?AQS 是什么?
  3. volatile 的作用?happens-before 原则?能保证原子性吗?
  4. 线程池核心参数?execute() vs submit()?拒绝策略?
  5. CompletableFuture 异步编排?
  6. 死锁如何产生?如何排查?(jstack + 线程 dump 分析)

如下所示:​​​​​​​​​​​​​​


1、synchronized 底层原理?锁升级过程?

        synchronized 是 JVM 提供的内置锁,基于 Monitor(监视器)对象实现,其底层依赖对象头的 Mark Word 和线程栈中的 Lock Record

一、底层原理:Monitor 机制

1. Monitor 是什么?

  • 每个 Java 对象关联一个 Monitor 对象(C++ 实现,位于 JVM 内部);
  • Monitor 包含:
    • _owner:当前持有锁的线程;
    • _count:重入次数;
    • _EntryList:阻塞等待的线程队列;
    • _WaitSet:调用 wait()的线程队列。

2. 字节码指令

  • 进入同步块:monitorenter
  • 退出同步块:monitorexit(正常退出 + 异常退出各一条)

💡 注意:方法级 synchronized 通过 ACC_SYNCHRONIZED 标志实现,无需显式指令。

二、锁升级过程(JDK 6+ 引入,单向不可逆)

JVM 为减少无竞争时的同步开销,设计了 4 级锁状态,根据竞争激烈程度动态升级。

如下所示:

三、详细升级流程

1. 无锁 → 偏向锁

  • 第一次加锁时,JVM 将 Mark Word 中的 Thread ID 设为当前线程 ID
  • 后续同一线程加锁:直接比对 Thread ID,无需 CAS;
  • 优点:无 CAS、无互斥,性能极高;
  • 缺点:多线程竞争时需撤销偏向(STW)。

🔧 参数控制

  • -XX:+UseBiasedLocking(默认开启)
  • -XX:+BiasedLockingStartupDelay=0(立即启用,避免 4s 延迟)

2. 偏向锁 → 轻量级锁

  • 当其他线程尝试加锁:
    • JVM 暂停持有偏向锁的线程;
    • 检查是否仍需偏向(如已退出同步块);
    • 若需竞争,撤销偏向锁,升级为轻量级锁;
  • 轻量级锁实现
    • 线程在栈中创建 Lock Record
    • CAS 将 Mark Word 替换为指向 Lock Record 的指针;
    • 成功则获得锁,失败则自旋重试。

3. 轻量级锁 → 重量级锁. 

  • 自旋一定次数(-XX:PreBlockSpin,默认 10 次)后仍失败;
  • 或有多个线程竞争(JVM 检测到竞争激烈);
  • 膨胀为重量级锁
    • Mark Word 指向 Monitor 对象;
    • 线程进入_EntryList 阻塞(OS 级挂起)。

⚠️ 注意

  • 轻量级锁不支持重入!重入会直接膨胀为重量级锁;
  • 偏向锁支持重入(通过 Thread ID + epoch)。

四、最佳实践

  • 提到 锁消除(Lock Elimination):JIT 发现锁无竞争,直接移除;
  • 提到 锁粗化(Lock Coarsening):合并相邻同步块,减少加锁次数;
  • 举例:在无竞争场景,偏向锁性能比 ReentrantLock 高 30%。

2、ReentrantLock vs synchronized?AQS 是什么?

一、ReentrantLock vs synchronized 对比

如下所示:

特性synchronizedReentrantLock
类型JVM 关键字JDK API 类
实现Monitor(对象头 Mark Word)AQS(AbstractQueuedSynchronizer)
锁获取自动手动(lock())
锁释放自动(代码块结束/异常)手动(unlock(),必须在 finally)
可中断✅(lockInterruptibly())
超时获取✅(tryLock(timeout))
公平锁✅(new ReentranLock(true))
多条件等待❌(仅一个 wait set)✅(多个 Condition)
性能(JDK 6+)≈(无竞争)≈(高竞争略优)

二、AQS(AbstractQueuedSynchronizer)是什么?

 JUC 包的基石,ReentrantLock、CountDownLatchSemaphore 等都基于它实现。

1. 核心设计

  • state:volatile. int,表示同步状态(如重入次数);
  • CLH 变种队列双向链表,存储等待线程(Node);
    • head:头节点(哑节点)
    • tail:尾节点
  • 模板方法模式
    • 子类实现 tryAcquire()/tryRelease();
    • AQS 负责线程排队、唤醒、中断处理。

2. Node 结构

static final class Node {volatile Node prev;volatile Node next;volatile Thread thread;Node nextWaiter; // Condition 队列
}

3. 加锁流程(以 ReentrantLock 为例)

  1. 调用  lock()→ acquire(1)
  2. tryAcquire():CAS 修改 state
    • 成功:获得锁
    • 失败:加入队列,park() 挂起  
  3. 释放锁时:unpark()  唤醒后继节点

三、公平锁 vs 非公平锁

  • 非公平锁(默认)
    • 新线程可直接抢锁,无需排队;
    • 吞吐量高,但可能造成线程饥饿。
  • 公平锁
    • 新线程必须排队;
    • 保证 FIFO,但吞吐量低(频繁上下文切换)。

代码如下所示:

// 非公平锁
final boolean acquire() {if (compareAndSetState(0, 1)) // 直接抢return true;return acquire(1);
}

四、最佳实践

  • 提到 StampedLock:读多写少场景,支持乐观读;
  • 举例:在限流器中用 tryLock(100,TimeUnit.MILLISECONDS) 实现非阻塞获取;
  • 强调:ReentrantLock 必须在 finally 中 unlock(),否则死锁。

3、volatile 的作用?happens-before 原则?能保证原子性吗?

一、volatile 的三大作用

1. 保证可见性

  • 写操作立即刷入主内存;
  • 读操作从主内存加载;
  • 避免线程读取“过期”副本。

2. 禁止指令重排序

  • 插入 内存屏障(Memory Barrier)
    • LoadLoad、StoreStore、LoadStore、StoreLoad;
  • 保证 volatile 写之前的代码不会重排到写之后。

3. 提供 happens-before 语义

  • volatile 写 happens-before 后续的 volatile 读。

二、happens-before 原则(JMM 核心)

happens-before 定义了操作间的可见性顺序,包括:

  1. 程序顺序规则:一个线程内,前面的操作 happens-before 后面的操作;
  2. 监视器锁规则:解锁 happens-before 后续加锁;
  3. volatile 变量规则:volatile 写 happens-before 后续 volatile 读;
  4. 线程启动规则:Thread.start()  happens-before 线程内任何操作;
  5. 线程 join 规则:线程内所有操作 happens-before  join() 返回。

💡 关键:happens-before ≠ 时间先后,而是结果可见性保证

三、volatile 能保证原子性吗?

不能!

1. 反例:i++

volatile int i = 0;
// 线程 A 和 B 同时执行
i++; // 包含:读 i → i+1 → 写 i
  • 两个线程可能同时读到 i = 0,最终 i = 1(应为 2);
  • 原因i++ 是复合操作,volatile 只保证单次读/写的原子性。

2. 何时可用?

  • 状态标志(如 volatile. boolean running);
  • 单次读/写(如 volatile Config config)。

3. 替代方案

  • 原子类:AtomicInteger(CAS + volatile);
  • 锁:synchronized / ReentrantLock。

四、最佳实践

  • 提到 DCL 单例(Double-Checked Locking) 必须用 volatile 防止重排序:
private volatile static Singleton instance;
public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton(); // 重排序风险!}}}return instance;
}
  • 强调:volatile 是轻量级同步机制,但绝不等于“线程安全”

4、线程池核心参数?execute() vs submit()?拒绝策略?

一、ThreadPoolExecutor 7 大核心参数

new ThreadPoolExecutor(int corePoolSize,      // 核心线程数(即使空闲也不销毁)int maximumPoolSize,   // 最大线程数long keepAliveTime,    // 非核心线程空闲存活时间TimeUnit unit,         // 时间单位BlockingQueue<Runnable> workQueue, // 任务队列ThreadFactory threadFactory,       // 线程工厂RejectedExecutionHandler handler   // 拒绝策略
);

1. 执行流程

  1. 线程数 < corePoolSize→ 创建新线程;
  2. 线程数 >= corePoolSize→ 入队 workQueue
  3. 队列满且线程数 < maximumPoolsize → 创建新线程;
  4. 队列满且线程数 = maximumPoolsize → 触发拒绝策略。

2. 常见线程池陷阱

  • Executors.newFixedThreadPool():无界队列 → OOM;
  • Executors.newCachedThreadPool():最大线程数 = Integer.MAX_VALUE → OOM。

最佳实践手动创建线程池,明确队列大小和拒绝策略。

二、execute() vs submit()

方法返回值异常处理适用场景
execute(Runnable)void未捕获异常会打印堆栈无需返回结果
submit(Runnable/Callable)Future<?>异常封装在 Future.get()需要返回结果或异常

💡 关键区别

  • submit() 内部将 Runnable 包装为 FutureTasks;
  • 调用 Future.get()时才会抛出任务中的异常。

三、4 种拒绝策略

如下所示:

策略行为适用场景
AbortPolicy(默认)抛出 RejectedExecutionException允许任务失败
CallerRunsPolicy由调用线程执行任务降低提交速度
DiscardPolicy静默丢弃任务任务可丢弃
DiscardOldestPolicy丢弃队列最老任务,重试提交保留最新任务

💡 自定义策略:实现 RejectedExecutionHandler 接口,如记录日志、降级处理。

四、最佳实践

  • 提到  allowCoreThreadTimeOut(true):核心线程也可超时销毁;
  • 举例:在秒杀系统中用 CallerRunsPolicy 降级,避免系统崩溃;
  • 强调:生产环境必须监控线程池指标(活跃线程数、队列大小、拒绝数)。

5、CompletableFuture 异步编排?

CompletableFuture 是 Java 8 引入的异步编程利器,支持链式调用、组合、异常处理。

一、核心优势

  • 非阻塞:避免 Future.get()阻塞;
  • 链式编排:thenApply/thenCompose/thenCombine;
  • 异常处理:exceptionally/handle;
  • 多任务组合:allOf/anyOf。

二、常用方法示例

1. 创建异步任务

代码如下所示:

// 异步执行(默认 ForkJoinPool)
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {return "Hello";
});// 指定线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {return "Hello";
}, executor);

2. 链式处理

代码如下所示:

CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> queryUser())          // 异步查用户.thenApply(user -> user.getName())       // 转换.thenCompose(name -> queryOrder(name))   // 扁平化(返回 CompletableFuture).thenCombine(queryAddress(), (order, addr) -> order + addr); // 合并两个异步结果

3. 异常处理

代码如下所示:

future.exceptionally(ex -> {log.error("Error", ex);return "Default Value";
});// 或 handle(同时处理正常和异常)
future.handle((result, ex) -> {if (ex != null) return "Error";else return result;
});

4. 多任务组合

代码如下所示:

CompletableFuture<Void> allDone = CompletableFuture.allOf(future1, future2, future3);
allDone.join(); // 等待所有完成CompletableFuture<Object> anyDone = CompletableFuture.anyOf(future1, future2);
Object result = anyDone.get(); // 返回第一个完成的结果

三、最佳实践

  • 提到 避免默认 ForkJoinPool:生产环境应指定自定义线程池;t
  • 举例:在订单服务中用 allOf 并行调用库存、优惠券、支付服务,RT 从 600ms 降至 200ms;
  • 强调:thenApply/thenCompose
    • thenApply:同步转换,返回普通值;
    • thenCompose:异步扁平化,返回 CompletableFutute。

6、死锁如何产生?如何排查?(jstack + 线程 dump 分析)

一、死锁产生的 4 个必要条件

  1. 互斥条件:资源不能共享(如锁);
  2. 请求与保持:持有资源的同时申请新资源;
  3. 不剥夺条件:资源不能被强制释放;
  4. 循环等待:存在进程-资源环形链。

💡 破坏任一条件即可避免死锁,如按序加锁(破坏循环等待)。

二、Java 死锁典型场景

代码如下所示:

// 线程 A
synchronized (lockA) {synchronized (lockB) { ... }
}// 线程 B
synchronized (lockB) {synchronized (lockA) { ... }
}

三、死锁排查步骤

1. 获取线程 dump

代码如下所示:

# 方式 1:jstack
jstack <pid> > thread_dump.txt# 方式 2:kill -3 <pid>(输出到 stdout)
# 方式 3:Arthas
thread -b  # 直接定位死锁线程

2. 分析死锁

  • 自动检测:jstack 会自动打印死锁信息(Found one Java-level deadlock);
  • 手动分析
    • 找到 Blocked 状态的线程;waiting to lock<address>
    • 查看 waiting to lock<address> 和  locked <address>;
    • 构建锁依赖图,找环。

3. 示例输出

Found one Java-level deadlock:
=============================
"Thread-1":waiting to lock monitor 0x00007f... (object 0x000000076b..., a java.lang.Object),which is held by "Thread-2"
"Thread-2":waiting to lock monitor 0x00007f... (object 0x000000076b..., a java.lang.Object),which is held by "Thread-1"

四、预防与解决

1. 预防

  • 按序加锁:所有线程以相同顺序获取锁;
  • 超时放弃:tryLock(timeout);
  • 避免嵌套锁:尽量减少同步块嵌套。

2. 监控

  • 使用 Arthas thread -b实时检测;
  • APM 系统监控线程 BLOCKED 状态。

五、最佳实践

  • 提到  jconsole/visualVM图形化检测死锁;
  • 举例:曾通过 jstack 发现数据库连接池死锁,修复后服务恢复;
  • 强调:死锁是“活锁”的一种,但更危险(永久阻塞)

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

相关文章:

  • 11种方法解决iPhone上共享相册不显示的问题[2025]
  • php网站制作教程美食网站开发的技术简介
  • Spring整合单元测试
  • 深入浅出理解克尔效应(Kerr Effect)及 MATLAB 仿真实现
  • 【区块链】二、案例1:汽车供应链区块链
  • 影刀RPA一键批量上传商品视频!AI智能处理,效率提升2000%[特殊字符]
  • 安卓/IOS工具开发基础教程:按键精灵一个简单的文字识别游戏验证
  • Python爬虫实战:澳元-人民币汇率历史数据获取与趋势分析
  • 网站开发属于大学那个专业网页设计题材
  • 图书馆网站建设的项目报告网站svg使用
  • 基于大数据的短视频流量数据分析与可视化
  • OT83211_VC1:4通道 ASRC OTG(44.1kHz~192kHz)音频采样率转换器产品介绍
  • 性能测试需求分析详解
  • Redis-面试问题
  • 小型网站开发用什么语言大型h5手游平台
  • 徐州网站建设工作室中国建筑官网首页
  • MyBatis注解的运用于条件搜索实践
  • 搭建网站 软件下载吴忠市建设局官方网站
  • 工厂方法模式:从理论到实战指南
  • 微信小程序 点击地图后弹出一个模态框
  • 3.6.6【2021统考真题】
  • 《道德经》第五十章
  • 分类问题的基石:逻辑回归(Logistic Regression)
  • 机器学习实践项目(二)- 房价预测增强篇 - 特征工程二
  • Jenkins自动部署CI/CD
  • 【unity】PowerVR GE8320系列GPU渲染问题分析
  • 做网站设计需要哪些知识网页游戏排行榜回合制
  • 从理论到实践:深度解析昇腾CANN训练营中的Ascend C编程模型
  • Java TreeMap与HashTable深度解析:有序映射与线程安全映射
  • 什么是大数据,为什么它很重要?