Java的锁机制问题

锁机制

1.锁监视器

在 Java 并发编程中,锁监视器(Monitor) 是对象内部与锁关联的同步机制,用于控制多线程对共享资源的访问。以下是核心要点:


🔒 监视器的核心组成

  1. 独占区(Ownership)

    • 一次仅允许一个线程持有监视器(即获得锁)
    • 通过 synchronized 关键字实现
  2. 入口区(Entry Set)

    • 竞争锁的线程队列(未获得锁的线程在此等待)
  3. 等待区(Wait Set)

    • 调用 wait() 的线程释放锁后进入此区域
    • 需通过 notify()/notifyAll() 唤醒

⚙️ 关键操作

操作作用触发条件
synchronized线程尝试获取监视器锁,成功则进入独占区,失败则阻塞在入口区进入同步代码块/方法时
wait()释放锁并进入等待区,线程状态变为 WAITING必须在持有锁时调用 (synchronized 内)
notify()随机唤醒一个等待区的线程(唤醒后需重新竞争锁)必须在持有锁时调用
notifyAll()唤醒所有等待区的线程必须在持有锁时调用

🔄 工作流程示例

public class MonitorDemo {private final Object lock = new Object(); // 锁监视器关联到此对象public void doWork() throws InterruptedException {synchronized (lock) { // 1. 线程进入独占区while (条件不满足) {lock.wait(); // 2. 释放锁并进入等待区}// 3. 执行临界区代码lock.notifyAll(); // 4. 唤醒其他等待线程}}
}

⚠️ 重要规则

  1. 锁绑定对象
    每个 Java 对象(包括 Class 对象)都有一个内置监视器

    synchronized(obj) { ... }  // 使用对象监视器
    synchronized(MyClass.class) { ... }  // 使用类对象的监视器
    
  2. wait/notify 必须持有锁
    未在 synchronized 块中调用会抛出 IllegalMonitorStateException

  3. 虚假唤醒(Spurious Wakeup)
    等待线程可能被随机唤醒(即使未调用 notify),必须用循环检查条件:

    while (!condition) { obj.wait();
    }
    

💡 监视器 vs 显式锁(ReentrantLock)

特性内置监视器ReentrantLock
实现方式JVM 内置JDK 代码实现 (java.util.concurrent)
锁获取隐式获取/释放显式调用 lock()/unlock()
等待条件单一等待队列支持多个 Condition 队列
公平性非公平锁可选公平/非公平模式
中断响应不支持支持 lockInterruptibly()

📌 总结

  • 监视器 = 锁 + 等待队列 + 线程调度机制
  • synchronized 实现锁的互斥访问
  • wait()/notify() 实现线程间协作
  • 经典应用场景:生产者-消费者模型、线程间条件等待

⚠️ 注意:优先使用 java.util.concurrent 包中的高级工具(如 ReentrantLockCondition),它们提供更灵活的同步控制,能避免许多内置监视器的限制。

2.锁升级

在 Java 并发编程中,锁升级(Lock Escalation) 是 JVM 对 synchronized 锁的优化机制,它会根据锁竞争情况动态调整锁状态,从低开销状态逐步升级到高开销状态。这是 Java 性能优化的关键特性(自 JDK 1.6 引入)。

🔄 锁升级的四个阶段

无锁
偏向锁
轻量级锁
重量级锁

1. 无锁状态(No Lock)

  • 特征:对象刚创建时,没有任何线程访问
  • 开销:无任何锁操作成本
  • 对象头标志001

2. 偏向锁(Biased Lock)

  • 适用场景单线程重复访问同步块
  • 优化原理
    • 在对象头记录首个获得锁的线程ID
    • 同一线程后续进入同步块时无需 CAS 操作
  • 对象头标志101
  • 升级触发:当其他线程尝试获取锁时

3. 轻量级锁(Lightweight Lock)

  • 适用场景多线程交替执行(无实际竞争)
  • 实现机制
    1. 在栈帧创建锁记录(Lock Record)
    2. 通过 CAS 将对象头替换为指向锁记录的指针
    3. 成功:获得锁;失败:自旋尝试
  • 对象头标志00
  • 升级触发:自旋超过阈值(默认10次)或自旋时出现第三个线程竞争

4. 重量级锁(Heavyweight Lock)

  • 适用场景高并发竞争
  • 实现机制
    • 通过操作系统 mutex 互斥量实现
    • 未获锁线程进入阻塞队列(涉及内核态切换)
  • 对象头标志10
  • 特点:开销最大,但保证公平性

🧪 锁升级过程示例

public class LockEscalationDemo {private static final Object lock = new Object();private static int counter = 0;public static void main(String[] args) {// 阶段1: 偏向锁 (单线程)synchronized (lock) {counter++;}// 阶段2: 轻量级锁 (多线程交替)new Thread(() -> {for (int i = 0; i < 5; i++) {synchronized (lock) { counter++; }}}).start();// 阶段3: 重量级锁 (高并发竞争)for (int i = 0; i < 10; i++) {new Thread(() -> {synchronized (lock) { counter++; }}).start();}}
}

📊 锁状态对比表

特性偏向锁轻量级锁重量级锁
适用场景单线程访问多线程交替执行高并发竞争
实现方式记录线程IDCAS自旋操作系统mutex
开销极低中等
竞争处理升级为轻量级锁自旋失败则升级线程阻塞
对象头存储线程ID+epoch指向栈中锁记录指针指向监视器对象指针
是否阻塞自旋(非阻塞)是(内核阻塞)
公平性可配置

⚙️ 锁升级关键技术细节

  1. Mark Word 结构变化

    // 32位JVM对象头示例
    | 锁状态   | 25bit          | 4bit     | 1bit(偏向) | 2bit(锁标志) |
    |----------|----------------|----------|------------|--------------|
    | 无锁     | 哈希码         | 分代年龄 | 0          | 01           |
    | 偏向锁   | 线程ID+epoch   | 分代年龄 | 1          | 01           |
    | 轻量级锁 | 指向锁记录指针 |          |            | 00           |
    | 重量级锁 | 指向监视器指针 |          |            | 10           |
    
  2. 批量重偏向(Bulk Rebias)

    • 当一类对象的偏向锁被撤销超过阈值(默认20次),JVM 会认为该类不适合偏向锁
    • 后续该类的对象会直接进入轻量级锁状态
  3. 锁消除(Lock Elision)

    • JIT 编译器对不可能存在共享竞争的锁进行消除

      // 示例:局部StringBuffer的同步会被消除
      public String localMethod() {StringBuffer sb = new StringBuffer(); // 局部变量sb.append("Hello");return sb.toString();
      }
      

⚠️ 重要注意事项

  1. 锁降级不存在

    • 锁升级是单向过程(偏向→轻量→重量)
    • 一旦升级为重量级锁,不会降级(即使竞争消失)
  2. 偏向锁延迟启动

    • JVM 启动后前 4 秒默认禁用偏向锁(避免初始化时的无效偏向)

      # 关闭偏向锁(JDK 15+默认)
      -XX:-UseBiasedLocking
      
  3. 自旋优化

    • 轻量级锁的自旋次数由 JVM 自适应调整(Adaptive Spinning)
    • 基于前一次锁获取的成功率动态变化

💡 最佳实践建议

  1. 低竞争场景

    • 保持默认设置(允许锁升级)
    • 避免不必要的同步块
  2. 高竞争场景

    • 考虑使用 ReentrantLock 替代 synchronized
    • 利用 java.util.concurrent 高级并发工具
  3. 性能调优

    # 查看锁竞争情况
    -XX:+PrintSynchronizationStatistics# 禁用偏向锁(若确认高竞争)
    -XX:-UseBiasedLocking
    

锁升级的本质:JVM 在线程安全执行效率之间寻找最佳平衡点,开发者应理解其原理但避免过度干预自动优化。

3.ABA问题

ABA 问题详解

在并发编程中,ABA 问题是使用 CAS(Compare-And-Swap)操作时可能遇到的一种经典问题。它发生在共享变量的值经历了 A→B→A 的变化序列后,CAS 操作无法检测到中间状态变化的情况。

🔍 ABA 问题发生机制

线程1 共享变量 线程2 读取值 A 修改值 A→B 修改值 B→A 执行CAS(A→C):成功! 线程1 共享变量 线程2

问题本质

  • CAS 只检查值是否匹配,不关心值是否被修改过
  • 虽然最终值回到了 A,但中间状态变化被忽略
  • 可能导致数据一致性问题

⚠️ 经典案例:无锁栈实现中的 ABA

public class Stack {private AtomicReference<Node> top = new AtomicReference<>();public void push(Node node) {Node oldTop;do {oldTop = top.get();node.next = oldTop;} while (!top.compareAndSet(oldTop, node));}public Node pop() {Node oldTop;Node newTop;do {oldTop = top.get();if (oldTop == null) return null;newTop = oldTop.next;} while (!top.compareAndSet(oldTop, newTop));return oldTop;}
}

ABA 问题发生场景:

  1. 线程1读取栈顶节点 A
  2. 线程1被挂起
  3. 线程2弹出 A,栈顶变为 B
  4. 线程2弹出 B
  5. 线程2压入 A(新节点,地址相同)
  6. 线程1恢复执行,CAS 成功将 A 替换为 C
  7. 结果:C.next 指向 B,但 B 已被弹出,造成内存错误

🛡️ ABA 问题解决方案

1. 版本号机制(推荐)

为每个状态变化添加版本号戳记:

// Java 内置解决方案
AtomicStampedReference<V> // 带整数戳记的引用
AtomicMarkableReference<V> // 带布尔标记的引用

实现原理

匹配
不匹配
版本戳
CAS操作
同时检查值和版本号
更新值和版本号
操作失败

使用示例

public class ABASolution {private AtomicStampedReference<Integer> value = new AtomicStampedReference<>(0, 0); // 初始值=0, 版本=0public void update(int expectedValue, int newValue) {int[] stampHolder = new int[1];int oldStamp;int newStamp;do {// 读取当前值和版本int currentValue = value.get(stampHolder);oldStamp = stampHolder[0];// 验证值是否被修改过if (currentValue != expectedValue) {break; // 值已被其他线程修改}newStamp = oldStamp + 1; // 更新版本号} while (!value.compareAndSet(expectedValue, newValue, oldStamp, newStamp));}
}

2. 不重复使用内存地址

  • 确保被替换的对象不会被重用
  • 适用于对象池或资源管理场景
  • 实现复杂,不推荐作为通用方案

3. 延迟回收(GC 语言中)

  • 依赖垃圾回收机制防止对象复用
  • 在非 GC 环境(如 C/C++)中不可靠

📊 ABA 问题与其他并发问题对比

问题类型发生场景检测难度典型解决方案
ABA 问题CAS 操作版本号机制
竞态条件多线程无序访问同步锁
死锁多锁相互等待锁排序、超时机制
活锁线程持续重试失败随机退避策略

ABA 问题本质:CAS 操作只能检查值的相等性,无法检测值的历史变化。版本号机制通过添加状态元数据,将值检查扩展为状态机检查,从而解决这一问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/85737.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/85737.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

老凤祥的AI智能眼镜:让智慧更近生活

在科技进步的潮流中,人工智能技术不断为我们的生活增添色彩。近日,有关字节跳动旗下的火山引擎与中国珠宝品牌老凤祥合作开发 AI 智能眼镜的消息引发了广泛关注。这款与众不同的眼镜因其独特的功能及技术支持,已经在业内引起了极大反响。 AI眼镜:老年群体的智能好帮手 根…

Kotlin 中为什么没有静态变量和静态方法—不用static?

Kotlin 的设计核心是&#xff1a; 一切皆对象&#xff1a;消除 static 的「非对象」特性&#xff0c;用 companion&#xff08;对象&#xff09;和顶层函数&#xff08;包级对象&#xff09;替代&#xff0c;让代码更统一。避免全局状态滥用&#xff1a;static 成员是全局可见…

VSCode性能调优:从卡顿到丝滑的终极方案

⚡ 核心价值 "这套配置使某金融核心系统VS Code内存占用从8GB降至1.2GB,加载速度提升15倍" —— 2024某银行效能优化报告 🧩 性能瓶颈拆解 一、百万行项目优化方案 🚀 黄金配置参数 // settings.json(核弹级优化) {"files.watcherExclude": {"…

以云织梦,渡数济世:辉瑞与亚马逊云科技共谱医药新乐章

胖头陀科技 编辑&#xff1a;沐由 【导读】“用合规的数据来帮助患者&#xff0c;成为患者回归健康的一味新药。”当下&#xff0c;在数字洪流的浪潮中&#xff0c;这味“良药”正沿着云和AI的脉络&#xff0c;奔向有需求的千家万户…… 如果说到Pfizer&#xff0c;估计十个人…

SpringBoot后端开发知识点总结(持续更新)

目录 1. 常用易混淆注解解释1.1 Resource和Autowired注解的区别1.2 PathVariable和RequestParam注解的区别 2. Mybatis-Plus高级特性2.1 强大的通用CRUD接口2.2 代码生成器 3. IDEA实用快捷键4. 前后端联调关键点4.1 代码示例4.2 联调要点4.3 调试技巧 1. 常用易混淆注解解释 …

电脑商城--用户收货管理

新增收货地址 1 新增收货地址-创建数据表 1.使用use命令先选中store数据库。 USE store; 2.在store数据库中创建t_address用户数据表。 CREATE TABLE t_address (aid INT AUTO_INCREMENT COMMENT 收货地址id,uid INT COMMENT 归属的用户id,name VARCHAR(20) COMMENT 收货人姓…

开发者避坑:接入Flux-Kontext API实现文生图、图生图功能

在数字化浪潮背景下&#xff0c;人工智能&#xff08;Artificial Intelligence, AI&#xff09;技术正加速重塑图像创作领域。智创聚合API平台近日宣布整合Flux-Kontext系列模型&#xff0c;通过API接口支持图生图和文生图功能&#xff0c;为开发者及创作者提供高效解决方案。此…

.Net Core 获取与bin目录相同文件路径的文件

在 .NET Core 中&#xff0c;您可以使用以下方法来获取与 bin 目录相同的文件路径。通常&#xff0c;bin 目录是应用程序编译后生成的输出目录&#xff0c;您可以使用 AppContext.BaseDirectory 或 Directory.GetCurrentDirectory() 来获取该目录的路径。 以下是一些常用的方法…

RN(React Native)技术应用中常出现的错误及解决办法

React Native 作为跨平台开发框架&#xff0c;在实际应用中可能会遇到一些常见的错误。以下是React Native 技术应用中常出现的错误及解决办法&#xff1a; 1. 网络请求失败&#xff08;Network Request Failed&#xff09; 原因&#xff1a; 请求地址不正确网络权限未配置i…

Java 21 的虚拟线程与桥接模式:构建高性能并发系统

Java 21 的虚拟线程与桥接模式&#xff1a;构建高性能并发系统 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 总有一行代码&#xff0c;能点亮万千星辰。 &#x1f50d; 在技术的宇宙中&#xff0c;我愿做永不停歇的探索者。 ✨ 用代码丈量世界&…

HTML5 火焰字体效果教程

HTML5 火焰字体效果教程 这里写目录标题 HTML5 火焰字体效果教程前言项目概述基本原理项目结构详细实现步骤1. HTML结构2. CSS样式3. JavaScript实现 代码详解1. 初始化设置2. 粒子系统3. 生成粒子4. 动画循环5. 交互控制 扩展和优化建议总结完整代码 前言 在这篇教程中&#…

SMOTE-XGBoost实战:金融风控中欺诈检测的样本不平衡解决方案

1. 行业问题背景 &#xff08;1&#xff09;金融欺诈检测的特殊性 在支付风控领域&#xff0c;样本不平衡是核心痛点。Visa 2023年度报告显示&#xff0c;全球信用卡欺诈率约为0.6%&#xff0c;但单笔欺诈交易平均损失高达$500。传统机器学习模型在此场景下表现堪忧&#xff1…

Instagram下载保存 -下载狗解析工具

在日常浏览Instagram时&#xff0c;是否有过这样的烦恼&#xff1a;看到一个精彩的视频&#xff0c;想要保存下来&#xff0c;却不知道如何操作&#xff1f;有时候我们会看到一些特别的旅行视频、搞笑片段&#xff0c;甚至是喜欢的名人分享的内容&#xff0c;简直是舍不得错过。…

flink如何基于Pekko实现RPC调用

摘要 通过阅读flink源码&#xff0c;了解flink是如何基于Pekko实现远程RPC调用的 Pekko实现远程调用 Flink 的 RPC 框架底层是构建在 Pekko 的 actor 模型之上的&#xff0c;了解Pekko如何使用&#xff0c;对后续源码的阅读有帮助。 Apache Pekko&#xff08;原为 Akka 的一…

Kafka节点注册冲突问题分析与解决

一、核心错误分析 ERROR Error while creating ephemeral at /brokers/ids/1, node already exists and owner does not match org.apache.zookeeper.KeeperException$NodeExistsException: KeeperErrorCode NodeExists问题本质&#xff1a;ZooKeeper中已存在ID为1的broker节…

突破PPO训练效率瓶颈!字节跳动提出T-PPO,推理LLM训练速度提升2.5倍

突破PPO训练效率瓶颈&#xff01;字节跳动提出T-PPO&#xff0c;推理LLM训练速度提升2.5倍 在大语言模型&#xff08;LLM&#xff09;通过长思维链&#xff08;CoT&#xff09;展现出强大推理能力的当下&#xff0c;强化学习&#xff08;RL&#xff09;作为关键技术却面临训练…

【Python】dictionary

1 字典功能 字典是可变容器模型&#xff0c;且可存储任意类型对象&#xff1b; 字典的每个键值对 <key: value> 用冒号 : 分割&#xff0c;每个对之间用逗号(,)分割&#xff0c;整个字典包括在花括号 {} 中 ,格式如下所示&#xff1a; d {key1 : value1, key2 : value…

【python】If 语句

1 使用if 进行条件判断 1.1 检查字符串是否相等 car bmw car BMW # FALSEcar bmw car.upper() BMW # true # 变小写用方法&#xff1a;lower1.2 检查字符串是否不相等 my_car yadeaif my_car ! Audi:print("Buy one! Buy one! Buy one!")1.3 比较数字 answe…

Knife4j 使用详解

一、概述 Knife4j 是一款基于 Swagger 的开源 API 文档工具&#xff0c;旨在为 Java 开发者提供更美观、功能更强大的 API 文档生成、展示和调试体验。它是 Swagger-Bootstrap-UI 的升级版&#xff0c;通过增强 UI 界面和扩展功能&#xff0c;解决了原生 Swagger UI 界面简陋、…

Java excel坐标计算

package com.common.base.util.excel;/*** excel 坐标计算*/ public class UtilExcelPosi {/*** deepseek生成 ExcelProperty(index UtilExcelPosi.pA)*/public final static int pA 0;public final static int pB 1;public final static int pC 2;public final static i…