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

JUC并发编程(五)volatile/可见性/原子性/有序性->JMM

目录

 一 可见性

二 原子性

三 有序性

四 volatile原理

1 如何保证可见性

2 如何保证有序性

3 无法解决指令交错(无法保证原子性)

4 双重检查锁(double-checked locking)

5 happens-before


总体概括

特性含义如何保障
原子性一个或多个操作要么全部执行成功,要么全部失败synchronizedAtomicInteger 等
可见性一个线程对共享变量的修改,其他线程能看到volatilesynchronizedfinal
有序性程序执行顺序与代码顺序一致volatilesynchronizedLock

 一 可见性

1 问题

代码展示:

package day01.neicun;public class example1 {static boolean flag = true;public static void main(String[] args) {Thread t1 = new Thread(() -> {while (flag) {}});t1.start();// 这里释放休眠,防止t1线程还未执行就被主线程给修改变量try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}flag = false;}
}

2 解决

  • 1 volatile易变关键字

它可以用来修饰成员变量和静态成员变量,可以避免线程从自己的工作缓存当中查找变量的值,必须到主存当中获取他的值,线程操作volatile变量都是直接操作主存。

线程不会再从缓存当中获取volatile的值,而是从主存当中获取,效率会有所下降,但是保证了共享变量在多个线程之间的可见性。

public class example1 {volatile static boolean flag = true;public static void main(String[] args) {Thread t1 = new Thread(() -> {while (flag) {}});t1.start();// 这里释放休眠,防止t1线程还未执行就被主线程给修改变量try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}flag = false;}
}
  • 2 使用synchroized

加锁(如 synchronizedReentrantLock)会强制线程从主内存中读取变量的最新值,而不是从本地缓存(工作内存)中读取。这是 Java 内存模型(JMM)保证可见性的核心机制之一。

package day01.neicun;public class example1 {static boolean flag = true;// 锁对象final static Object obj = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {//这里会在执行完判断后将锁释放,那修改变量的同步代码块就可以运行了while (true) {synchronized (obj) {if (flag) {break;}}}});t1.start();// 这里释放休眠,防止t1线程还未执行就被主线程给修改变量try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (obj){flag = false;}}
}

二 原子性

机制原理适用场景特点
synchronized互斥锁需要保护临界区代码块或方法JVM内置,简单易用,可能阻塞
显式锁 (Lock)更灵活的互斥锁需要高级锁特性(可中断、公平等)更灵活,需手动释放锁
原子类CAS + volatile对单个变量进行原子操作无锁,高性能
不可变对象状态不可修改共享只读数据无同步开销,最安全

三 有序性

有序性是指:程序执行的顺序按照代码的先后顺序执行。

1 指令重排

指令重排(Instruction Reordering)是现代CPU和编译器为了提高程序执行效率而采用的关键优化技术。它通过重新排列指令的执行顺序来最大化利用CPU资源,但可能引发多线程环境下的可见性问题。

现代处理器会设计为一个时钟周期完成一条执行时间最长的CPU指令。

1 使用volatile的情况

public class ConcurrencyTest {int num = 0;volatile boolean ready = false;public void actor1(I_Result r) {if (ready) { // 读取 volatile 变量,确保看到最新的值r.r1 = num + num;} else {r.r1 = 1;}}public void actor2(I_Result r) {num = 2; // 写操作ready = true; // 写 volatile 变量,确保前面的操作已完成}
}

四 volatile原理

volatile 关键字的底层原理是 内存屏障(Memory Barriers) 机制。

volatile 解决的是 "一个线程写,其他线程读" 时的有序性和可见性问题,而解决 "多个线程同时写" 需要更强的同步机制。

  • 对volatile变量的写指令后会加入写屏障
  • 对volatile变量的读指令前会加入读屏障

总的来说写屏障保障之前的写and重排,读屏障保障之后的读and重排

1 如何保证可见性

  • 写屏障保证在该屏障之前的,对变量的改动都同步到主存当中。

  • 读屏障保证在该屏障之后对变量的读取,加载的是主存中的最新数据。

2 如何保证有序性

  • 写屏障会确保指令重排序时,不会将屏障之前的代码排在写屏障之后。

  • 读屏障会确保指令重排序时,不会将屏障之后的代码排在读屏障之前。

3 无法解决指令交错(无法保证原子性)

volatile 解决的是 "一个线程写,其他线程读" 时的有序性和可见性问题,而解决 "多个线程同时写" 需要更强的同步机制(结合锁机制确保)。

  • 写屏障仅仅是保证之后的读取能读取到最新的结果,但是不能保证读跑到他前面去。
  • 而有序性的保证也只是保证了本线程内相关代码不被重排序。

4 双重检查锁

双重检查锁(double-checked locking)的核心目的---以单例为例

1. 双重检查的作用

双重检查锁(Double-Checked Locking)的核心目的是:

  • 性能优化:第一次检查避免不必要的同步开销。
  • 线程安全:第二次检查确保在同步块内只有一个线程能创建实例。
代码示例
public final class Singleton {// 1. volatile 修饰的静态实例private static volatile Singleton instance;// 2. 私有构造函数private Singleton() {}// 3. 双重检查的获取实例方法public static Singleton getInstance() {if (instance == null) {                  // 第一次检查(无锁)synchronized (Singleton.class) {     // 同步块if (instance == null) {          // 第二次检查(有锁)instance = new Singleton();  // 创建实例}}}return instance;}
}

一些知识点

  1. final 修饰类的原因
    防止子类化破坏单例性,确保全局唯一性。

  2. 实例变量 private 的原因
    强制通过工厂方法访问,防止外部直接修改实例状态。(private为类内部)

  3. 实例变量 volatile 的原因
    禁止指令重排序(解决部分构造问题),保证跨线程可见性。

  4. 构造函数 private 的原因
    禁止外部实例化,确保单例控制权唯一。

  5. 两次 null 判断的原因
    首次检查(无锁)避免性能开销;二次检查(同步块内)防止重复创建。

  6. 类对象加锁的原因
    类锁保证全局唯一性,且独立于实例生命周期,避免死锁风险。

  7. 类的实现目的
    实现高性能线程安全单例:延迟加载、双重检查锁定、全局唯一实例访问。

破坏单例模式

1 反射方式

通过反射强行调用私有构造函数(setAccessible(true)),绕过单例的静态实例控制逻辑,直接创建新对象。

2 反序列化

反序列化时,ObjectInputStream 默认通过反射调用无参构造器新建对象,而非返回单例的静态实例。

普通方式解决

package day01.vola;import java.io.Serial;
import java.io.Serializable;public final class Singleton implements Serializable {// 1. volatile 修饰的静态实例private static volatile Singleton instance;// 2. 私有构造函数private Singleton() {// 防止反射破坏单例if (instance != null) {throw new RuntimeException("单例类禁止通过反射创建实例!");}}// 3. 双重检查的获取实例方法public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}// 4. 防止反序列化破坏单例@Serialprivate Object readResolve(){return instance;}
}

枚举方式解决

为何可以实现单例并解决反序列化和反射而导致的问题

  1. 实例数量固定:枚举在编译期确定所有实例,运行时无法创建新实例

  2. 类加载机制:枚举实例在首次访问时由 JVM 静态初始化(线程安全)

  3. JVM 底层保障

    • 禁止反射创建枚举实例(Constructor.newInstance() 直接抛出异常)

    • 特殊序列化机制(仅存储枚举名,反序列化通过 Enum.valueOf() 还原)

  4. 语法限制

    • 枚举构造器强制私有化

    • 不能显式实例化(编译器阻止 new 操作)

代码实现:

package day01.vola;public enum Single {// 唯一单例实例(名称可自定义)INSTANCE;// 单例状态字段(自动线程安全)private int requestCount = 0;// 单例业务方法public void handleRequest() {requestCount++;System.out.println("Handled request #" + requestCount);}// 可添加静态业务方法public static void warmUp() {System.out.println("Singleton initialized");}
}

静态内部类实现单例

5 happens-before

happens-before规定了对共享变量的写操作对其他线程的读操作可见,他是可见性与有序性的一套规则总结。

 八大规则

规则示例作用
程序顺序规则x=1; y=2; → x 先于 y单线程操作顺序保留
监视器锁规则解锁 → 后续加锁synchronized 可见性保证
volatile规则写 volatile → 后续读禁止重排序 + 跨线程可见
线程启动规则thread.start() → 线程内操作父线程配置对子线程可见
线程终止规则线程结束 → thread.join()子线程操作对父线程可见
传递性规则A→B 且 B→C ⇒ A→C跨操作链式可见
中断规则interrupt() → 检测到中断中断信号可靠传递
finalize规则构造结束 → finalize()对象完整构造后才回收

http://www.xdnf.cn/news/923941.html

相关文章:

  • 基于 GWAS 的群体遗传分析将 bZIP29 确定为玉米中的异种基因
  • QT学习教程(二十一)
  • redis主从复制
  • go中的接口返回设计思想
  • AI Agent 与 Agentic AI 企业实践
  • 湖北理元理律师事务所:债务优化中的民生保障实践
  • 【C/C++】std::vector成员函数清单
  • 力扣HOT100之二分查找:33. 搜索旋转排序数组
  • Docke启动Ktransformers部署Qwen3MOE模型实战与性能测试
  • 如何理解ES6模块化方案的缓存机制?
  • SpringBoot离线应用的5种实现方式
  • 【python】RGB to YUV and YUV to RGB
  • 使用python实现奔跑的线条效果
  • 【八股消消乐】MySQL存储引擎InnoDB知识点汇总
  • 深入解析快速排序算法:原理、优化与应用
  • java内存模型JMM
  • 图上合成:用于大型语言模型持续预训练的知识合成数据生成
  • Python: 告别 ModuleNotFoundError, 解决 pipx 环境下 sshuttle 缺少 pydivert 依赖的终极指南
  • Redis Key过期策略
  • 关于 ​​Thread 与 Runnable​​ 以及 ​​线程生命周期​​ 的详细说明与示例
  • Protobuf 中的类型查找规则
  • ADB识别手机系统弹授权框-如何处理多重弹框叠加和重叠问题
  • 现代C++特性(一):基本数据类型扩展
  • Unity的日志管理类
  • 东芝Toshiba e-STUDIO2110AC打印机信息
  • 用电脑通过USB总线连接控制keysight示波器
  • csrf攻击学习
  • Java 8 Stream API 入门到实践详解
  • Robot Framework 一个通用的 自动化测试框架
  • 【DAY42】Grad-CAM与Hook函数