"/>
侧边栏壁纸
博主头像
PySuper 博主等级

千里之行,始于足下

  • 累计撰写 286 篇文章
  • 累计创建 17 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

Java 并发安全 一

PySuper
2022-11-12 / 0 评论 / 0 点赞 / 12 阅读 / 0 字
温馨提示:
所有牛逼的人都有一段苦逼的岁月。 但是你只要像SB一样去坚持,终将牛逼!!! ✊✊✊

前置理论

可重入锁

  1. 核心定义与特性

    • 允许同一线程多次获取同一把锁而不会死锁

    • 重入计数器:每次获取锁时计数器递增,释放时递减,归零时锁完全释放

    • 线程标识绑定:锁内部记录当前持有锁的线程,确保只有持有者能重入

    • 避免递归死锁:线程在递归或嵌套方法中调用同一锁保护的代码时无需重复等待

  2. 与不可重入锁的对比

    • 不可重入锁:同一线程重复获取锁时会阻塞,导致死锁(如自旋锁在递归中无限等待)

    • 可重入锁优势:通过计数器机制安全重复进入同步区域,提升代码灵活性

  3. Java 中的实现与应用

    • ReentrantLock

      • 显式锁,支持公平性选择(公平锁按请求顺序分配,非公平锁允许插队)

      • 支持条件变量(Condition)实现精准线程唤醒(如生产者-消费者模型的队列分离)

    • synchronized 关键字

      • 隐式可重入锁,功能有限(不支持中断或超时)

  4. 典型应用场景

    • 递归算法:递归函数多次获取同一锁保护共享资源

    • 同步嵌套调用:类中多个方法互斥访问共享资源且存在调用链

    • 复杂线程协作:通过 Condition 实现多等待队列(如任务调度优先级控制)

  5. 注意事项

    • 锁释放匹配:每次获取锁必须对应释放,否则计数器无法归零导致死锁

    • 性能权衡:公平锁减少线程饥饿但增加切换开销,非公平锁反之

    • 避免过度嵌套:深层嵌套增加调试复杂度(需追踪计数器状态)

核心策略

多线程安全保障的核心策略

  1. 同步机制

    • 互斥锁

      • synchronized:自动管理锁,适用于简单临界区(如计数器操作)

      • ReentrantLock:支持可中断锁、公平锁及条件变量,适合复杂同步需求(如超时控制)

    • 读写锁

      • ReentrantReadWriteLock:读共享、写独占,提升读多写少场景性能(如缓存系统)

  2. 无锁编程

    • 原子类

      • AtomicInteger/AtomicReference:基于 CAS 实现无锁线程安全操作(如高并发计数)

    • 可见性控制

      • volatile:保证变量修改的可见性,适用于简单状态标志(需结合锁保证原子性)

  3. 线程隔离与不可变设计

    • 线程局部存储

      • ThreadLocal:为每个线程提供独立变量副本(如会话管理),需调用 remove() 防泄漏

    • 不可变对象

      • 对象状态不可修改(如 String、不可变集合),天然线程安全

  4. 线程安全数据结构

    • 并发集合

      • ConcurrentHashMap:分段锁优化高并发读写,性能优于 HashTable

      • CopyOnWriteArrayList:写时复制机制,适合读多写少场景(如事件监听器列表)

    • 阻塞队列

      • BlockingQueue:自动阻塞管理(如 LinkedBlockingQueue),简化生产者-消费者模型

  5. 并发工具类

    • 任务协调工具

      • CountDownLatch:主线程等待多个子任务完成(如服务初始化)

      • CyclicBarrier:多线程同步至共同阶段后继续执行(如分批次数据处理)

    • 资源控制工具

      • Semaphore:控制资源并发访问量(如数据库连接池限流)

  6. 执行框架类

    • 线程池管理

      • ThreadPoolExecutor:灵活配置线程池参数(核心线程数、队列策略等)

    • 异步任务管理

      • FutureTask:封装异步计算结果,支持阻塞获取结果(如并行计算任务)

  7. 锁与条件类

    • 高级锁机制

      • StampedLock:乐观读锁,减少读写锁竞争(读多写少场景优化)

    • 条件变量

      • Condition:与 ReentrantLock 配合,实现细粒度等待/唤醒(如生产者-消费者模型)

  8. 设计原则与优化

    • 锁粒度控制

      • 仅同步必要代码块(如共享变量操作),减少锁竞争时间

    • 死锁预防

      • 统一锁顺序、设置超时(tryLock)、使用检测工具(如 jstack

    • 性能优化策略

      • 读多写少场景优先用无锁或乐观锁,高并发场景结合线程池管理

公平锁 & 非公平锁

  1. 公平锁

    • 获取顺序:严格按线程请求顺序分配锁,遵循“先到先得”原则,释放锁时队列中等待最久的线程优先获取

    • 实现机制

      • 维护 FIFO 等待队列,通过 hasQueuedPredecessors() 检查队列状态

      • 示例:Java 的 ReentrantLock(true) 在获取锁前检查队列

    • 特点

      • 优点:避免线程饥饿

      • 缺点:性能较低,因维护队列和线程切换开销增加

  2. 非公平锁

    • 获取顺序:允许线程直接竞争锁,新请求线程可能插队抢占锁,无需检查队列

    • 实现机制

      • 直接尝试 CAS 获取锁,失败后才加入队列

      • 示例:Java 的 ReentrantLock() 默认模式

    • 特点

      • 优点:吞吐量高,减少线程调度开销

      • 缺点:可能导致线程饥饿

  3. 核心区别

    • 锁分配顺序:公平锁严格按队列顺序,非公平锁允许插队

    • 性能:非公平锁性能更高(减少线程切换),公平锁适合对公平性敏感的场景

    • 实现复杂度:公平锁需维护队列状态,非公平锁实现更简单

  4. Java 中的实现

    • ReentrantLock:通过构造函数参数 true 启用公平锁,默认或 false 为非公平锁

    • synchronized:底层固定为非公平锁,不支持配置

  5. 适用场景

    • 公平锁:需严格顺序的场景(如银行转账、任务调度)

    • 非公平锁:高并发且锁竞争不激烈(如缓存读取、短任务处理)

吞吐量

非公平锁吞吐量为什么比公平锁大?

  1. 减少上下文切换开销

    • 非公平锁允许新线程直接抢占锁,避免公平锁强制唤醒队列头部线程的上下文切换(耗时 1-10 微秒)

    • 刚释放锁的线程可能立即重新获取,减少线程状态切换次数

  2. 利用 CPU 缓存局部性

    • 非公平锁允许刚释放锁的线程(缓存未失效)快速重入,避免公平锁切换线程导致的缓存重新加载(延迟 100-300 纳秒)

  3. 避免队列维护成本

    • 非公平锁仅在竞争失败时加入队列,公平锁每次获取需检查队列状态(增加 20-50 纳秒/次开销)

    • 实验数据:64 线程时非公平锁吞吐量下降幅度比公平锁低 60%-80%

  4. 减少线程挂起概率

    • 锁持有时间短时,非公平锁有 30%-50% 概率让新线程直接获取锁,避免挂起和内核调度介入(耗时 1-5 微秒)

  5. 优化系统调用频率

    • 非公平锁通过 CAS 自旋减少 park() 系统调用次数,相同并发量下系统调用比公平锁减少 40%-70%

    • 高并发测试(64 线程)中吞吐量可达公平锁的 5-8 倍

死锁问题

  1. 死锁产生的核心条件

    • 互斥条件:资源被独占使用(如数据库写锁)

    • 请求与保持:持有资源的同时请求新资源(如线程A持有锁1后请求锁2)

    • 不可剥夺:资源只能由持有者主动释放

    • 循环等待:形成等待链(如线程A→B→C→A)

  2. 典型死锁场景

    • 多线程嵌套锁:线程以不同顺序获取多个锁(如线程1锁A后锁B,线程2锁B后锁A)

    • 数据库事务冲突:事务交叉更新记录(如事务T1更新X后请求Y,T2更新Y后请求X)

    • 资源分配不合理:短任务高频竞争不可剥夺资源(如内存不足时进程互相等待)

  3. 死锁解决方法

    • 预防策略

      • 统一资源申请顺序:按固定编号顺序获取锁(如先锁表A再锁表B)

      • 一次性申请所有资源:启动前声明所需全部资源

    • 动态避免

      • 银行家算法:动态检测资源分配安全性

      • 超时机制:设置锁获取超时(如 tryLock(5, TimeUnit.SECONDS)

    • 检测与恢复

      • 资源分配图检测:周期性扫描环路并终止部分进程

      • 事务回滚:数据库自动回滚代价最小的事务

    • 工程实践优化

      • 减少锁粒度:使用读写锁(如 ReentrantReadWriteLock

      • 无锁编程:采用 CAS(如 AtomicInteger)或线程本地存储

  4. 不同场景的解决方案选择

    • 高并发服务:非公平锁+超时检测(如电商秒杀系统用 ReentrantLock

    • 金融交易系统:统一资源访问顺序+事务隔离级别(按账户ID升序更新)

    • 实时操作系统:资源抢占机制(允许高优先级进程回收资源)

synchronized锁

定义

  1. 核心作用

    • 通过内置锁机制确保多线程环境下共享资源的原子性和可见性

    • 基于对象监视器(Monitor)控制线程对同步代码的访问,同一时刻仅允许一个线程执行锁定代码

  2. 三种使用方式

    • 修饰实例方法

      • 锁对象:当前实例(this)。

      • 场景:保护实例变量的线程安全操作,如多线程操作同一对象的计数器

    • 修饰静态方法

      • 锁对象:类的 Class 对象(ClassName.class

      • 场景:保护静态变量的线程安全操作,如全局计数器或单例初始化

    • 修饰代码块

      • 锁对象:可指定任意对象(如 this、自定义锁)

      • 场景:缩小锁粒度提升性能,仅对关键代码段加锁

使用

  1. 典型应用场景

    • 共享资源的原子操作

      • 需保证复合操作完整性的场景,如计数器增减、转账操作、库存扣减

    • 单例模式的双重检查锁定

      • 延迟初始化且保证线程安全,如全局配置类、数据库连接池

    • 线程安全的数据结构

      • 保护集合类的并发修改,如 Collections.synchronizedList 包装的 ArrayList

    • 生产者-消费者模型

      • 协调线程间的数据传递,如消息队列的任务生产和消费

  2. 注意事项

    • 锁粒度控制

      • 避免全方法加锁,优先同步关键代码段以减少竞争

    • 死锁预防

      • 统一多锁的获取顺序(如按对象哈希值排序),避免循环等待

    • 性能优化

      • 减少锁持有时间,利用 JVM 锁升级机制(偏向锁→轻量级锁→重量级锁)

    • 异常处理

      • finally 块中释放锁,防止线程阻塞导致资源泄漏

非公平锁

  1. synchronized 的公平性本质

    • 底层实现默认且固定为非公平锁,不保证线程按请求顺序获取锁

    • 新请求线程可能直接抢占锁,而非遵循队列顺序

  2. 非公平锁的核心表现

    • 随机竞争机制:锁释放时 JVM 不检查队列顺序,新线程与队列线程随机竞争

    • 潜在饥饿风险:高频短任务场景中可能出现线程长期无法获取锁

  3. 设计选择的原因

    • 性能优先:减少线程唤醒和上下文切换(每次 1-10 微秒),吞吐量可达公平锁的 5-8 倍

    • 实现简化:锁升级机制(偏向锁→轻量级锁→重量级锁)针对非公平场景优化

  4. 与 ReentrantLock 的对比

    • 灵活性差异:ReentrantLock 可配置公平/非公平模式,synchronized 仅支持非公平

    • 适用场景

      • synchronized:锁持有时间短、竞争不激烈(如计数器递增)

      • ReentrantLock 公平模式:需严格顺序的场景(如银行转账)

静态 & 普通

synchronized 锁,静态方法和普通方法区别?

  1. 锁对象不同

    • 普通方法:锁定当前实例对象(this),同一实例的同步普通方法互斥,不同实例方法可并行执行

    • 静态方法:锁定类的 Class 对象,所有实例调用同步静态方法均互斥,全局仅一个线程可执行

  2. 作用范围差异

    • 普通方法:仅对同一对象实例的同步方法互斥,不同实例的同名方法不受影响

    • 静态方法:同一时间任何实例的同步静态方法均不可并行执行

  3. 锁的粒度

    • 普通方法:锁粒度较细,影响单个实例操作,适合高频实例级资源保护

    • 静态方法:锁粒度较粗,全局锁定类级别资源,可能成为性能瓶颈

  4. 适用场景

    • 普通方法:保护实例变量(如对象属性修改),避免多线程数据竞争

    • 静态方法:保护静态变量或实现类级同步逻辑(如单例模式双重检查锁定)

  5. 线程交互影响

    • 普通方法:不同线程访问不同实例的同步普通方法可并发执行

    • 静态方法:同一时间仅一个线程能执行静态同步方法(如全局配置更新)

  6. 锁类型独立性

    • 互不干扰:类锁与对象锁可同时持有(如线程持有类锁后仍可获取对象锁)

    • 锁升级机制:JVM 对两种锁均可能进行偏向锁、轻量级锁、重量级锁优化升级

支持重入

  1. synchronized 支持重入

    • 同一线程可多次获取同一对象的锁而不会阻塞,避免递归或嵌套调用导致的死锁

  2. 实现原理

    • 锁计数器与监视器机制

      • 对象关联锁计数器(status)和持有线程 ID,首次获取计数器置 1,重入时递增,释放时递减,归零后完全释放

    • 字节码指令支持

      • 通过 monitorentermonitorexit 指令控制代码块进入/退出时的锁状态检查与计数器更新

    • 锁升级机制

      • 无竞争时使用偏向锁记录线程 ID,竞争升级为轻量级锁(CAS 自旋),激烈竞争时转为重量级锁(操作系统互斥锁)

  3. 优势与局限

    • 优势

      • 自动管理锁释放,避免死锁风险

      • 支持递归调用和嵌套同步代码块,简化编程逻辑

    • 局限

      • 高并发场景下频繁锁升级(如偏向锁→重量级锁)可能带来性能损耗

  4. 与显式锁的对比

    • synchronized 的可重入性是隐式自动管理,而 ReentrantLock 需手动管理计数器

锁升级

注意高版本因为偏向锁性能收益低和实现过于复杂,已将偏向锁移除

  1. 无锁状态

    • 对象创建时无锁,Mark Word 无锁标记,所有线程可自由访问

  2. 偏向锁阶段

    • 触发条件:第一个线程首次获取锁时,JVM 将对象头 Mark Word 设为偏向锁状态(锁标志位 01),记录线程 ID

    • 优化目的:单线程重复获取锁时无需同步操作,直接通过线程 ID 比对进入同步代码块

    • 撤销机制:其他线程尝试获取锁时,JVM 暂停持有线程,若锁已释放则升级为轻量级锁

  3. 轻量级锁阶段

    • 触发条件:多线程低竞争时升级(锁标志位 00)

    • 实现方式

      • 线程栈帧创建锁记录(Lock Record),拷贝对象头 Mark Word

      • 通过 CAS 替换对象头为指向锁记录的指针,成功则获取锁,失败则自旋等待

    • 自旋优化:自适应策略动态调整自旋次数以减少 CPU 空转

  4. 重量级锁阶段

    • 触发条件:自旋次数超限或竞争激烈时升级(锁标志位 10)

    • 实现机制

      • 依赖操作系统互斥量(Mutex)实现阻塞与唤醒,未获锁线程进入等待队列

      • 涉及用户态到内核态切换,带来高上下文切换开销

    • 不可逆性:升级后无法降级,即使竞争减弱仍保持该状态

  5. 锁升级的意义

    • 性能优化:减少低竞争场景同步开销(如偏向锁单线程优化)

    • 平衡机制:轻量级锁减少阻塞,重量级锁保障高并发有序执行

    • Mark Word 动态变化:对象头存储不同元数据(线程 ID、锁记录指针等)

JVM优化

  1. 锁升级机制

    • 轻量级锁

      • 通过 CAS 操作和线程栈的锁记录实现非阻塞同步,首次获取时拷贝对象头 Mark Word 至锁记录,CAS 替换对象头指针

      • 自适应自旋策略动态调整自旋次数,减少 CPU 空转

    • 重量级锁

      • 高竞争时升级为操作系统互斥锁(Mutex),依赖等待队列管理线程阻塞与唤醒

      • 用户态到内核态切换带来高开销,升级后不可逆

  2. 锁消除

    • JIT 编译器通过逃逸分析检测同步对象是否仅限当前线程使用,未逃逸时消除锁操作

    • 典型场景:未逃逸的局部变量(如方法内的 StringBuffer)同步被优化为无锁形式

  3. 锁粗化

    • 合并相邻同步代码块减少加锁/解锁开销,如循环体内多次同步合并为单次同步

    • 优化原则:临界区执行时间需远小于线程调度时间片(通常控制在 1 毫秒内)

  4. 自适应锁优化

    • 动态选择锁策略:低竞争时自旋等待,高竞争时直接阻塞线程

    • 自旋次数阈值基于历史竞争数据动态调整,平衡 CPU 利用率与响应速度

  5. 偏向锁替代方案

    • 现代 JVM(JDK 15+)移除偏向锁,优先采用锁消除和轻量级锁优化

    • 遗留支持:通过 -XX:+UseBiasedLocking 强制启用(官方不推荐)

  6. 其他优化策略

    • Mark Word 复用:对象头动态存储线程 ID、锁记录指针等元数据

    • 可重入性:通过锁计数器实现同一线程多次获取锁,避免递归死锁

    • 锁粒度控制:低竞争用细粒度锁,高竞争改用显式锁(如 ReentrantLock)

ReentrantLock

定义

  1. 定义

    • ReentrantLock 的定义

  2. 特性

    • 可重入性:同一线程可多次获取同一锁,避免递归调用或嵌套同步块导致的死锁

    • 公平性选择:支持公平锁(按请求顺序分配)和非公平锁(默认模式),前者减少线程饥饿但吞吐量较低

    • 条件变量:通过 newCondition() 创建多个等待队列,实现细粒度线程协作(如生产者-消费者模型的不同唤醒条件)

    • 锁超时与中断响应:提供 tryLock(timeout, unit)lockInterruptibly(),支持超时获取锁和中断等待

    • 锁状态监控:通过 getHoldCount() 查询锁重入次数,isLocked() 判断锁是否被占用

使用

  1. 应用场景

    • 高竞争环境下的共享资源保护

      • 多线程操作计数器、转账逻辑等需要原子性更新的场景,相比 synchronized 在高并发下性能更优

    • 需要公平调度的系统

      • 任务调度需按提交顺序处理请求的场景(如金融交易系统)

    • 复杂线程协作模型

      • 生产者-消费者模型中结合 Condition 实现满队列和空队列的精准唤醒

      • 阻塞队列通过 tryLock 控制入队/出队的超时逻辑

    • 框架级并发控制

      • 保护线程池状态(如 ThreadPoolExecutorRUNNING 状态)和任务队列的线程安全

      • ConcurrentHashMap 的分段锁机制中使用 ReentrantLock 控制哈希桶的并发访问

    • 需要锁中断或超时响应的场景

      • 分布式锁实现中通过 tryLock 避免线程因网络问题长期阻塞。

      • 实时系统要求任务在指定时间内完成并执行降级策略

  2. 注意事项

    • 手动释放锁:必须在 finally 块中调用 unlock(),否则可能导致死锁或资源泄漏

    • 避免锁嵌套:支持可重入但过度嵌套会增加锁竞争复杂度

    • 公平锁的权衡:仅在严格顺序需求时使用,避免增加线程切换开销

    • 性能监控:通过 getQueueLength() 监控等待线程数,优化锁竞争热点

公平锁

  1. 公平锁的核心实现机制

    • FIFO 队列顺序

      • 通过 FairSync 类实现,线程调用 lock() 时必须先检查 AQS 队列是否存在等待线程

        • 队列为空或无前驱节点时尝试 CAS 获取锁

        • 队列存在等待线程时,当前线程加入队列尾部排队

  2. 关键方法 hasQueuedPredecessors()

    • tryAcquire() 中调用该方法检查队列状态

    • 确保只有队列头部线程能获取锁,阻止新线程插队

  3. CLH 队列管理

    • AQS 维护 CLH 双向链表队列,公平锁通过 enq() 将竞争失败的线程封装为 Node 节点加入队列尾部

    • 锁释放时唤醒队列头部节点的线程,保证顺序执行

  4. 与非公平锁的差异

    • 初始抢占逻辑

      • 公平锁:必须检查队列状态后才尝试 CAS

      • 非公平锁:直接 CAS 抢占,不检查队列

    • 性能表现

      • 公平锁吞吐量较低(约下降 5-8 倍),适合顺序敏感场景

      • 非公平锁适合短任务竞争,吞吐量更高

    • 线程饥饿风险

      • 公平锁无饥饿问题,非公平锁可能引发线程长期等待

  5. 适用场景与代价

    • 适用场景:支付系统、任务调度等需严格顺序的场景

    • 实现代价

      • 每次锁获取需队列检查(增加 20-50 纳秒开销)

      • 频繁线程切换导致性能下降(对比非公平锁吞吐量降低 60%-80%)

CountDownLatch

同步工具

  1. 核心功能

    • 允许一个或多个线程等待其他线程完成特定操作后再继续执行

    • 内部维护计数器,初始化时指定等待的线程数量,countDown() 减少计数,计数器归零时唤醒等待线程

  2. 工作原理

    • 初始化计数器new CountDownLatch(n) 设置初始值 n(需等待的任务数)

    • 任务完成通知:子线程调用 countDown() 安全减少计数(基于 CAS 实现)

    • 阻塞与唤醒:主线程 await() 阻塞至计数器归零,支持超时 await(timeout, unit)

  3. 典型应用场景

    • 主线程等待子任务完成:服务启动时等待所有组件初始化完毕

      CountDownLatch latch = new CountDownLatch(3);  
      
      // 启动 3 个子线程初始化服务并调用 latch.countDown()  
      latch.await(); // 阻塞至所有服务就绪  
    • 多线程结果汇总:并行计算后主线程汇总结果(如分片数据处理)

    • 并发测试同步:确保所有并发任务执行完毕后再验证结果

    • 资源协调:学生全部离场后通知家长,顾客到齐后服务员上菜

  4. 注意事项

    • 一次性使用:计数器归零后无法重置,需重新创建

    • 异常处理:子线程在 finally 中调用 countDown() 避免主线程永久阻塞

    • 超时机制:建议设置 await() 超时防止系统僵死

    • 精确计数:初始值与实际任务数严格匹配(过多导致阻塞,过少提前唤醒)

AQS

定义

  1. AQS 的基本概念与核心功能

    • 定义:Java 并发包(JUC)的核心框架,用于构建锁和同步器(如 ReentrantLock、Semaphore、CountDownLatch)

    • 核心功能

      • 通过 volatile int state 管理资源状态,支持原子操作(getState()/setState()/CAS)

      • 基于 CLH 队列实现线程排队与唤醒,通过 LockSupport 实现线程阻塞(park())与唤醒(unpark()

  2. 底层实现原理

    • 同步队列(CLH 队列)

      • 结构:双向链表,节点(Node)包含线程、等待状态(waitStatus)及前后指针

      • 作用:存储未获取资源的线程,实现 FIFO 公平调度,头节点(head)表示当前持有资源的线程

    • 条件队列

      • 单向链表,用于 Condition 的等待/通知机制(如 await()signal()

  3. 资源共享模式

    • 独占模式

      • 同一时刻仅一个线程访问资源(如 ReentrantLock),通过 tryAcquire()/tryRelease() 实现

      • 可重入性:state 记录同一线程多次获取锁的计数

    • 共享模式

      • 允许多线程同时访问资源(如 Semaphore、CountDownLatch),通过 tryAcquireShared()/tryReleaseShared() 实现

  4. 典型应用场景

    • 锁实现:ReentrantLock 重写 tryAcquire() 实现公平/非公平锁

    • 同步器

      • CountDownLatchstate 初始化为计数值,countDown() 递减,归零时唤醒所有等待线程

      • Semaphore:通过 state 控制并发线程数,acquire()/release() 操作许可

  5. 优势与设计特点

    • 模板方法模式:子类仅需实现 tryAcquire()/tryRelease() 等模板方法,AQS 自动处理队列管理与调度

    • 性能优化

      • 双向链表支持快速删除节点(如线程超时或中断时)

      • 自旋优化减少上下文切换开销

    • 可扩展性:通过组合 AQS 实现复杂同步策略(如读写锁分离)

可重入公平锁

  1. 核心实现原理

    • 公平性保证

      • 通过 AQS 的 CLH 队列实现线程 FIFO 排队,在 tryAcquire() 中调用 hasQueuedPredecessors() 检查队列是否有等待线程

    • 可重入性

      • 通过 state 变量记录锁持有次数,同一线程多次获取时递增 state,释放时递减至归零

  2. 关键实现步骤

    • 定义 Sync 类继承 AQS

      • 创建 ReentrantFairLock 类,内部定义 Sync 类继承 AbstractQueuedSynchronizer

    • 重写 tryAcquire 方法

      • 检查 state == 0:若无等待线程且 CAS 设置 state 成功,获取锁并设置独占线程

      • 若当前线程是持有者,state += acquires 实现可重入

    • 重写 tryRelease 方法

      • 递减 state,归零时清空独占线程标识

  3. 公平锁与非公平锁区别

    • 公平锁:强制检查队列状态,仅无等待线程时尝试获取锁

    • 非公平锁:允许直接 CAS 抢占锁,无需检查队列

  4. 锁操作流程

    • 加锁:lock() 调用失败时,线程进入 CLH 队列并阻塞,等待前驱节点唤醒

    • 解锁:unlock() 释放锁后,唤醒队列中下一个线程

  5. 性能与注意事项

    • 公平性代价:获取锁需遍历队列,吞吐量低于非公平锁

    • 重入限制lock()unlock() 次数需严格匹配,否则导致死锁或状态异常

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区