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

网站建设与管理教学视频教程网站站点怎么做

网站建设与管理教学视频教程,网站站点怎么做,东莞网络做推广公司,介绍网站设计风格一、Java 内存模型(JMM)深度解析1.1 什么是 Java 内存模型?Java 内存模型(Java Memory Model,JMM)是 Java 并发编程的核心基础,它定义了多线程环境下程序的执行规则。JMM 并非指代物理内存结构&…

一、Java 内存模型(JMM)深度解析

1.1 什么是 Java 内存模型?

Java 内存模型(Java Memory Model,JMM)是 Java 并发编程的核心基础,它定义了多线程环境下程序的执行规则。JMM 并非指代物理内存结构,而是 Java 虚拟机规范中定义的一组抽象规则和约束。这些规则主要解决以下三个关键问题:

  1. 可见性问题:在多核 CPU 环境下,由于每个 CPU 都有独立的缓存,一个线程对共享变量的修改可能不会立即被其他线程看到。
  2. 原子性问题:某些操作需要多个步骤完成(如i++),在并发环境下可能会被其他线程打断。
  3. 有序性问题:编译器和处理器为了优化性能可能会对指令进行重排序,可能破坏多线程程序的正确性。

JMM 的设计目标是在保证程序正确性的前提下,为编译器和处理器的优化提供足够的灵活性。它通过定义 happens-before 关系来规范线程间的交互行为。

1.2 JMM 的抽象结构

主内存(Main Memory)

主内存是所有线程共享的内存区域,存储了:

  • 实例对象的成员变量(非final)
  • 静态变量(类变量)
  • 数组元素
  • 被volatile修饰的变量

主内存的特点是访问速度相对较慢,但存储容量大。

工作内存(Working Memory)

每个线程都有自己独立的工作内存,存储了:

  • 线程私有的局部变量
  • 方法参数
  • 异常处理器参数
  • 从主内存拷贝的变量副本

工作内存的特点是访问速度快,但容量有限,且对其他线程不可见。

内存交互操作

JMM 定义了8种原子操作来规范主内存和工作内存间的交互:

  1. lock(锁定):作用于主内存变量,标识变量为线程独占状态
  2. unlock(解锁):释放锁定状态的变量
  3. read(读取):从主内存传输变量值到工作内存
  4. load(载入):将read得到的值放入工作内存变量副本
  5. use(使用):把工作内存变量值传递给执行引擎
  6. assign(赋值):将执行引擎接收的值赋给工作内存变量
  7. store(存储):将工作内存变量值传送到主内存
  8. write(写入):将store得到的值放入主内存变量

这些操作必须按顺序成对出现(如read后必须load),但允许在中间插入其他操作。

1.3 JMM 解决的三大核心问题

1.3.1 可见性问题

典型场景:在多核CPU环境下,每个CPU核心都有自己的缓存,当线程A修改了共享变量时,可能只更新了CPU缓存而尚未写回主内存,导致线程B读取到旧值。

解决方案

  • 使用volatile关键字
  • 使用synchronized同步块
  • 使用final变量(初始化后不可变)
  • 使用java.util.concurrent包中的原子类

扩展示例

public class VisibilitySolution {private volatile boolean flag = false;  // 添加volatile保证可见性public void setFlag() {flag = true;}public void getFlag() {while (!flag) {// 现在能正确感知flag变化}}
}

1.3.2 原子性问题

典型场景:像i++这样的复合操作实际上包含三个步骤:

  1. 读取i的值
  2. 将i的值加1
  3. 将新值写回i

在并发环境下,这些步骤可能会被其他线程打断。

解决方案

  • 使用synchronized同步块
  • 使用java.util.concurrent.atomic包中的原子类
  • 使用Lock接口的实现类

扩展示例

public class AtomicitySolution {private AtomicInteger i = new AtomicInteger(0);  // 使用原子类public void increment() {i.incrementAndGet();  // 原子性递增}public static void main(String[] args) throws InterruptedException {AtomicitySolution demo = new AtomicitySolution();ExecutorService executor = Executors.newFixedThreadPool(2);for (int j = 0; j < 2; j++) {executor.submit(() -> {for (int k = 0; k < 5000; k++) {demo.increment();}});}executor.shutdown();executor.awaitTermination(1, TimeUnit.SECONDS);System.out.println(demo.i.get());  // 现在保证输出10000}
}

1.3.3 有序性问题

典型场景:在单线程环境下,指令重排序不会影响程序结果,但在多线程环境下可能导致逻辑错误。

解决方案

  • 使用volatile关键字(禁止重排序)
  • 使用synchronized同步块
  • 使用final变量
  • 使用java.util.concurrent中的并发工具类

扩展示例

public class OrderingSolution {private volatile static int a = 0, b = 0;  // 添加volatile防止重排序private volatile static int x = 0, y = 0;public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10000; i++) {a = 0; b = 0; x = 0; y = 0;Thread t1 = new Thread(() -> {a = 1;x = b;});Thread t2 = new Thread(() -> {b = 1;y = a;});t1.start(); t2.start();t1.join(); t2.join();// 现在不会出现x=0且y=0的情况if (x == 0 && y == 0) {System.out.println("出现有序性问题:x=0,y=0");break;}}}
}

1.4 内存屏障:JMM 的底层保障

1.4.1 内存屏障的类型

屏障类型作用描述典型使用场景
LoadLoad确保屏障前的load操作先于屏障后的load操作执行读取volatile变量后的操作
StoreStore确保屏障前的store操作先于屏障后的store操作执行写入volatile变量前的操作
LoadStore确保屏障前的load操作先于屏障后的store操作执行读取volatile变量后的写操作
StoreLoad确保屏障前的store操作先于屏障后的load操作执行,且会刷新所有CPU缓存写入volatile变量后的任何操作

1.4.2 JMM 对内存屏障的使用规则

  1. volatile变量

    • 写操作前插入StoreStore屏障
    • 写操作后插入StoreLoad屏障
    • 读操作前插入LoadLoad屏障
    • 读操作后插入LoadStore屏障
  2. synchronized

    • 进入同步块时插入LoadLoad和LoadStore屏障
    • 退出同步块时插入StoreStore和StoreLoad屏障
  3. final字段

    • 构造函数中对final字段的写操作后插入StoreStore屏障
    • 读取final字段前插入LoadLoad屏障

实际应用示例

public class MemoryBarrierDemo {private int x;private volatile boolean ready;public void writer() {x = 42;                    // 普通写ready = true;              // volatile写,会插入StoreStore和StoreLoad屏障}public void reader() {if (ready) {               // volatile读,会插入LoadLoad和LoadStore屏障System.out.println(x); // 保证看到x=42}}
}

二、volatile 关键字全面剖析

2.1 volatile 的核心特性

volatile 是 Java 提供的轻量级同步关键字,仅能修饰变量(成员变量或静态变量),无法修饰方法或代码块。其核心特性包括:

2.1.1 保证可见性

当 volatile 变量被修改时,JMM (Java 内存模型) 会强制将修改后的值立即写回主内存;同时,其他线程读取该变量时,会强制从主内存重新加载最新值,而非使用工作内存中的旧副本。这一机制确保了多线程环境下变量的实时可见性。

解决的问题:在前文的 VisibilityDemo 示例中,若将 flag 声明为 volatile,线程 B 能立即看到线程 A 对 flag 的修改,从而避免无限循环。具体来说,当线程 A 将 flag 从 false 修改为 true 时,这个变化会立即对所有线程可见,线程 B 能及时检测到这个变化并终止循环。

2.1.2 禁止重排序

JMM 对 volatile 变量的读写操作施加重排序限制,这些限制通过内存屏障实现:

  1. volatile 写操作:禁止将写操作后的指令重排序到写操作之前;
  2. volatile 读操作:禁止将读操作之前的指令重排序到读操作之后;
  3. volatile 读写锁:一个线程的 volatile 写操作与另一个线程的 volatile 读操作之间,禁止重排序。

解决的问题:在前文 OrderingDemo 中,若将 a、b 声明为 volatile,可避免重排序导致的 x=0 且 y=0 的异常结果。这是因为 volatile 确保了写操作的有序性,防止了指令重排序可能带来的问题。

2.1.3 不保证原子性

volatile 仅能保证单个变量的读写原子性,无法保证复合操作(如 i++、i+=1)的原子性。

原因分析:复合操作包含多个步骤(读取、计算、写入),volatile 无法阻止这些步骤被其他线程中断。例如 i++ 操作实际上包含读取 i 的值、对 i 加 1、将结果写回 i 三个步骤,这些步骤可能被其他线程打断。

示例验证:将 AtomicityDemo 中的 i 声明为 volatile 后,多线程执行 increment() 仍可能出现结果小于 10000 的情况。例如,两个线程同时读取 i 的值为 5,各自加 1 后写回 6,导致实际只增加了一次,这就是典型的原子性问题。

2.2 volatile 的实现原理

volatile 的特性依赖于 CPU 内存屏障指令和 JMM 内存屏障规则的协同工作,具体实现如下:

2.2.1 字节码层面的标识

当变量被声明为 volatile 时,Java 编译器会在字节码中添加 ACC_VOLATILE 标识,告知 JVM 该变量是 volatile 类型,需按 volatile 规则处理。

示例字节码

// 变量声明:private volatile int i = 0;// 对应的字节码
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field i:I (带有ACC_VOLATILE标识)
9: return

2.2.2 底层的内存屏障插入

JVM 根据 volatile 变量的读写操作,在生成机器码时插入对应的内存屏障,具体规则如下:

操作类型内存屏障插入规则
volatile 写操作1. 在写操作前插入 StoreStore 屏障(确保之前的 store 操作已完成);2. 在写操作后插入 StoreLoad 屏障(确保写操作已写回主内存)。
volatile 读操作1. 在读操作前插入 LoadLoad 屏障(确保之后的 load 操作读取最新值);2. 在读操作后插入 LoadStore 屏障(确保读操作已完成)。

2.2.3 CPU 层面的缓存一致性协议

除内存屏障外,CPU 的缓存一致性协议(如 MESI 协议)也为 volatile 的可见性提供支持。当一个 CPU 核心修改了缓存中的 volatile 变量副本,会通过总线广播通知其他核心该变量已失效,其他核心需重新从主内存加载最新值。这个过程确保了所有 CPU 核心都能看到该变量的最新值。

2.3 volatile 的使用场景

volatile 并非万能,需在特定场景下使用才能发挥其价值,主要适用以下场景:

2.3.1 状态标志位

用于多线程间传递状态(如"停止信号""初始化完成"),确保状态变更立即被其他线程感知。

示例代码

public class StopThreadDemo {// volatile状态标志位private volatile boolean isStop = false;public void run() {while (!isStop) {// 执行任务System.out.println("线程运行中...");}System.out.println("线程已停止");}// 其他线程调用此方法停止任务线程public void stop() {isStop = true;}
}

2.3.2 单例模式的双重检查锁(DCL)

在单例模式的双重检查锁实现中,volatile 用于禁止实例化对象时的指令重排序,避免获取到未初始化完成的对象。

错误示例(无 volatile)

public class Singleton {private static Singleton instance; // 无volatileprivate Singleton() {}public static Singleton getInstance() {if (instance == null) { // 第一次检查synchronized (Singleton.class) {if (instance == null) { // 第二次检查// 问题:对象实例化分为三步,可能重排序导致instance非null但未初始化instance = new Singleton();}}}return instance;}
}

问题原因分析:new Singleton() 包含三步指令:

  1. 分配对象内存(memory = allocate());
  2. 初始化对象(ctorInstance(memory));
  3. 将内存地址赋值给 instance(instance = memory)。

若发生重排序(如 1→3→2),其他线程可能在第一次检查时看到 instance != null,但实际对象未初始化完成,导致空指针异常。

正确示例(加 volatile)

public class Singleton {// 加volatile禁止重排序private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

2.3.3 避免指令重排序的关键变量

在多线程环境中,若某个变量的读写顺序直接影响程序逻辑正确性,可使用 volatile 禁止重排序。例如,初始化配置信息时,确保配置变量初始化完成后再对外提供访问。

2.4 volatile 与 synchronized 的区别

volatile 和 synchronized 都是 Java 中的同步工具,但在特性和使用场景上有显著区别:

对比维度volatilesynchronized
修饰对象仅变量(成员变量、静态变量)方法、代码块(锁对象可为任意对象)
原子性不保证(仅单个变量读写原子)保证(锁范围内的所有操作原子)
可见性保证保证(锁释放时写回主内存,锁获取时加载主内存)
有序性保证(禁止重排序)保证(锁范围内的操作视为单线程执行)
性能轻量级,无锁竞争,开销低重量级,可能存在锁竞争和线程阻塞,开销高
使用场景状态标志、DCL 单例、禁止重排序复合操作、多变量同步、临界区保护

选择建议:当只需要保证变量可见性或禁止重排序时,优先使用 volatile;当需要保证复合操作的原子性或需要同步多个变量时,应使用 synchronized。

三、常见问题与面试考点

3.1 为什么 volatile 不能保证原子性?

volatile 关键字仅能保证单个变量的读写操作不被编译器和处理器重排序,并且修改后的值立即对其他线程可见。但 volatile 无法保证复合操作的原子性,这是因为复合操作包含多个步骤,而这些步骤在执行过程中可能被其他线程打断。

以典型的 i++ 操作为例,它实际上包含三个步骤:

  1. 读取变量 i 的当前值到线程的工作内存
  2. 对读取的值进行加1运算
  3. 将新值写回主内存

假设线程A和线程B同时执行i++操作:

  • 线程A读取i=0,执行加1运算(此时i=1)
  • 线程B也读取i=0(因为线程A尚未写回),执行加1运算(i=1)
  • 两个线程先后写回结果,最终i=1而非预期的2

这种情况被称为"竞态条件",而volatile无法解决这类问题。要保证原子性,需要使用synchronized或Atomic类等同步机制。

3.2 volatile 变量的读写操作是否会加锁?

volatile变量的读写操作完全不会加锁,这是它与synchronized关键字的本质区别。volatile的实现原理是:

  1. 通过内存屏障(Memory Barrier)保证指令执行的顺序性:

    • 写操作后插入StoreStore和StoreLoad屏障
    • 读操作前插入LoadLoad和LoadStore屏障
  2. 依赖CPU的缓存一致性协议(如MESI):

    • 当volatile变量被修改时,会立即将缓存行置为无效状态
    • 其他CPU核心读取时必须从主内存重新加载

这种机制相比锁有以下优势:

  • 无锁竞争,不会导致线程阻塞
  • 无上下文切换开销
  • 适合读多写少的场景

但在高争用情况下,频繁的缓存失效会导致性能下降。

3.3 双重检查锁单例中,volatile 的作用是什么?

在双重检查锁定(DCL)模式中,volatile的主要作用是防止对象初始化过程中的指令重排序问题。具体来说:

public class Singleton {private volatile static Singleton instance;public static Singleton getInstance() {if (instance == null) {                     // 第一次检查synchronized (Singleton.class) {if (instance == null) {             // 第二次检查instance = new Singleton();     // 关键行}}}return instance;}
}

不加volatile时,new Singleton()可能被重排序为:

  1. 分配对象内存空间
  2. 将引用赋值给instance变量(此时instance!=null)
  3. 执行构造函数初始化

如果线程A执行到步骤2后被挂起,线程B看到instance非null就直接返回,但实际上对象还未初始化完成,导致NPE。

volatile通过禁止这种重排序,确保:

  1. 分配内存
  2. 初始化对象
  3. 赋值引用 三个步骤按顺序执行,从而保证其他线程获取到的都是完全初始化的对象。

3.4 多线程环境下,volatile 变量和普通变量的读写性能差异?

volatile变量的读写性能确实比普通变量低,主要原因包括:

  1. 内存屏障引入的开销:

    • 写操作需要插入StoreLoad屏障(x86架构约消耗几十个时钟周期)
    • 读操作需要插入LoadLoad屏障
    • 这些屏障会阻止CPU的指令级并行优化
  2. 缓存一致性协议的开销:

    • 每次写操作都会导致其他CPU核心的缓存行失效
    • 需要等待总线事务完成(尤其在多核环境下)
    • 可能引发"缓存行乒乓"现象
  3. 不能使用寄存器优化:

    • 普通变量可能被编译器优化为寄存器访问
    • volatile变量必须每次都从内存读取

实测数据示例(基于x86_64):

  • 普通变量读:~1ns
  • volatile变量读:~5ns
  • 普通变量写:~1ns
  • volatile变量写:~10ns

但与synchronized相比(通常需要100ns以上),volatile仍然是轻量级的同步方案。适合用于:

  • 状态标志位(如shutdown信号)
  • 一次性发布不可变对象
  • 读远多于写的计数器等场景
http://www.dtcms.com/a/608304.html

相关文章:

  • 上海网站建设联系电话网站建设费用计算依据
  • 做调查问卷的网站知乎专业视频剪辑培训机构
  • 邯郸网站开发公司东莞营销型网站建设公司
  • 织梦dedecms绿色led照明公司企业网站模板 下载网站设计用户体验
  • 郑州服务设计公司网站稳定的网站建设
  • 新建网站如何调试做网站必须要认证吗
  • 网站开分站建设广州公司网站
  • 网站的二级栏目怎么做狠友紧急升级访问页面
  • xampp怎么做网站wordpress js放到oss
  • 网站开发 平台建设站长之家网站
  • 网站建设的流程该怎么确定wordpress插件图片无法加载
  • 智慧养老网站开发有限责任公司成立条件
  • 做网站需要字体切换搭建网站做财务系统
  • 微信手机网站流程做网站被骗怎么办
  • python 做网站速度新闻视频网站开发
  • 如何做简洁网站小程序制作模板网站
  • 河南国正建设集团公司网站wordpress中文网站模板
  • 四川科隆建设有限公司网站做软件推广网站怎么赚钱
  • 杭州手机网站制作公司哪家好如何保护网站名
  • cms门户网站模板下载各大网站查重率比较
  • 建设网站服务器选择wordpress的密码加密
  • 不用代码的网站建设做搜狐网站页面
  • 霸屏网站开发企业网站一般做多宽
  • 网站开发要跑道吗godaddy wordpress托管
  • 做项目接任务的网站网站建设十年杜绝模板
  • 小超人成都网站建设珠海网站建设服务
  • 公司建立自己的网站吗百度网页
  • 安徽省经工建设集团网站中国建筑装饰百强排名
  • 网站的毕业设计怎么做中国最大的网站建设
  • 十堰网站优化价格优酷网站怎么做的