volatile的应用

1.volatile的概念

volatile通常被称为轻量级的synchronized,他与synchronized不同,volatile只修饰变量,无法修饰方法和代码块。

用法:只需要在声明一个可能被多线程同时访问的变量时,使用volatile修饰就可以了。

特点:volatile在线程安全方面,可以保证有序性和可见性,但是不能保证原子性。

synchronized 可以保证原子性,因为synchronized修饰的代码片段,在进入之前加了锁,只要它没执行完,其他线程无法获得锁执行这段代码片段,就可以保证他内部的代码可以被全部被执行。进而保证了原子性。

过程:

        为了提高 理速度, 理器不直接和内存 行通信,而是先将系 内存的数据 到内部 存( L1 L2 或其他)后再 行操作,但操作完不知道何 会写到内存。如果 声明了 volatile 的变量 行写操作, JVM 就会向 理器 送一条 Lock 的指令,将 量所在 存行的数据写回到系统内存。为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

2.volatile如何保证可见性的

        对于volatile变量,当对volatile变量进行写操作的时候,JVM会向处理器发送一条lock前缀的
指令,将这个缓存中的变量回写到系统主存中。
        所以,如果一个变量被volatile所修饰的话,在每次数据变化之后,其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个volatile在并发编程中,其值在多个缓存中是可见的。

        MESI的核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

3.volatile如何保证有序性

        volatile除了可以保证数据的可见性之外,还有一个强大的功能,那就是他可以禁止指令重排
优化等。
        普通的变量仅仅会保证在该方法的执行过程中所依赖的赋值结果的地方都能获得正确的结果,而不能保证变量的赋值操作的顺序与程序代码中的执行顺序一致。volatile是通过内存屏障来禁止指令重排的,这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被volatile修饰的变量的操作,会严格按照代码顺序执行,load->add->save 的执行顺序就是:load、add、save。如经典的双重校验锁必须加volatile的问题,就是因为volatile加了内存屏障。

4.为什么volatile不保证原子性

volatile仅保证单个操作的原子性:在Java中,volatile仅保证对单个volatile变量的读操作和写操作是原子的。这意味着,当一个线程读取或写入一个volatile变量时,这个操作是不可中断的。但是,这并不意味着复合操作(如i++)是原子的。

复合操作的非原子性:对于像i++这样的复合操作,它实际上包含三个步骤:读取i的值、将i的值加1、将新值写回i。由于volatile只能保证每个步骤的原子性,而不能保证整个复合操作的原子性,因此当多个线程同时执行这样的操作时,就可能出现数据不一致的问题。

内存可见性与原子性的区别:volatile通过内存屏障等机制保证了变量的内存可见性,即当一个线程修改了volatile变量的值后,这个新值对其他线程是立即可见的。然而,这并不意味着volatile能够保证复合操作的原子性。内存可见性和原子性是两个不同的概念,前者关注的是变量值的传播速度,而后者关注的是操作的不可分割性。

不保证原子性案例:

对于像 i++ 这样的复合操作,即使加了 volatile 修饰符,也仍然可能出现数据不一致的情况。这是因为 volatile 仅能保证单个读写操作的原子性,而无法保证复合操作的原子性。

复合操作 i++ 实际上包含三个步骤:

  1. 读取 i 的当前值。
  2. 将读取到的值加 1。
  3. 将新值写回 i

这三个步骤在单线程环境中是顺序执行的,因此不会出现问题。但在多线程环境中,如果多个线程同时执行 i++ 操作,就可能发生竞态条件(race condition),导致数据不一致。

具体来说,如果两个线程几乎同时执行 i++,它们可能会:

  • 线程A读取 i 的当前值(假设为0)。
  • 线程B在线程A读取之后但写入之前也读取 i 的值(此时仍然是0,因为线程A的写入尚未发生)。
  • 线程A将加1后的值(1)写回 i
  • 线程B也将其读取到的值(0)加1,并将结果(1)写回 i,覆盖了线程A的写入。

最终,尽管两个线程都执行了 i++ 操作,但 i 的值只增加了1,而不是预期的2。

总结:

volatile修饰的共享变量,其他线程进行读操作时,总能读到最新的数据。

volatile修饰的共享变量,其他线程进行写操作时,直接写在主内存中,并不是缓存。

volatile保证数据的可见性和顺序性。

相关推荐

  1. volatile应用

    2024-07-17 05:32:01       23 阅读
  2. volatile作用

    2024-07-17 05:32:01       53 阅读
  3. volatile

    2024-07-17 05:32:01       40 阅读
  4. synchronized和volatile区别

    2024-07-17 05:32:01       48 阅读
  5. Synchronized和volatile区别

    2024-07-17 05:32:01       53 阅读
  6. c语言中volatile

    2024-07-17 05:32:01       49 阅读
  7. volatile关键字作用、原理

    2024-07-17 05:32:01       39 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-17 05:32:01       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-17 05:32:01       71 阅读
  3. 在Django里面运行非项目文件

    2024-07-17 05:32:01       58 阅读
  4. Python语言-面向对象

    2024-07-17 05:32:01       69 阅读

热门阅读

  1. MySQL 分库分表

    2024-07-17 05:32:01       25 阅读
  2. C# 文件上传总结

    2024-07-17 05:32:01       23 阅读
  3. Linux的线程

    2024-07-17 05:32:01       22 阅读
  4. Transformer模型在多任务学习中的革新应用

    2024-07-17 05:32:01       25 阅读
  5. 【Qt+opencv】ROI与图像混合

    2024-07-17 05:32:01       25 阅读
  6. Jmeter二次开发Demo

    2024-07-17 05:32:01       23 阅读
  7. ubuntu上通过修改grub启动参数,将串口重定向到sol

    2024-07-17 05:32:01       23 阅读
  8. 【mysql】02在ubuntu24安装并配置mysql

    2024-07-17 05:32:01       26 阅读
  9. 常见云存储服务对比

    2024-07-17 05:32:01       19 阅读
  10. Linux基础 -- 运行安全之ASLR的作用与实现方式

    2024-07-17 05:32:01       28 阅读
  11. Django获取request请求中的参数

    2024-07-17 05:32:01       26 阅读
  12. 上传文件给Ubuntu服务器

    2024-07-17 05:32:01       27 阅读