【JUC并发编程】Volatile关键字底层原理(上)(Volatile的特性/ CPU多核硬件架构剖析/ JMM内存模型/ JMM八大同步规范/ Volatile的底层实现原理)


1. 什么是 Volatile

volatile是Java提供的轻量级的同步机制,保证了可见性,不保证原子性 禁止重排序。
Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。
volatile能解决多个cpu中高速缓存(工作内存)数据的一致性问题

2. Volatile的特性

可见性
可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的

顺序性
程序执行程序按照代码的先后顺序执行。

原子性
原子是世界上的最小单位,具有不可分割性。

3. Volatile的用法

public class Demo extends Thread {
    /**
     * lock 锁 汇编的指令 强制修改值,立马刷新主内存中 另外线程立马可见刷新主内存数据
     */
    private static volatile boolean FLAG = true;

    @Override
    public void run() {
        while (FLAG) {

        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Demo().start();
        Thread.sleep(1000);
        FLAG = false;
    }
}

4. CPU多核硬件架构剖析

CPU每次从主内存读取数据比较慢,而现代的CPU通常涉及多级缓存,CPU读主内存
按照空间局部性原则加载。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5. CPU的摩尔定律

https://baike.baidu.com/item/%E6%91%A9%E5%B0%94%E5%AE%9A%E5%BE%8B/350634?fr=aladdin
基本每隔18个月,可能CPU的性能会提高一倍。

6. JMM内存模型

Java内存模型定义的是一种抽象的概念,定义屏蔽java程序对不同的操作系统的内存访问差异。

6.1 主内存

存放我们共享变量的数据

6.2 工作内存

每个CPU对共享变量(主内存)的副本。

7. JMM八大同步规范

在这里插入图片描述

1.read(读取):作用于 主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用;从主内存读取数据
2.load(载入):作用于 工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中; 将主内存读取到的数据写入工作内存中
3.use(使用):作用于 工作内存的变量,把工作内存中的一个变量值传递给执行引擎;从工作内存读取数据来计算
4. assign(赋值):作用于 工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量;将计算好的值重新赋值到工作内存中
5.store(存储):作用于 工作内存的变量,把工作内存中的一个变量的值传送到 主内存中,以便随后的write的操作;将工作内存数据写入主内存
6.write(写入):作用于 工作内存的变量,它把store操作从工作内存中的一个变量的值传送到 主内存的变量中; 将store过去的变量值赋值给主内存中的变量
7.lock(锁定):作用于 主内存的变量,把一个变量标记为一条线程独占状态; 将主内存变量加锁,标识位线程独占状态。
8. unlock(解锁):作用于 主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;将主内存变量解锁,解锁后其他线程可以锁定该变量

8. Volatile的底层实现原理

通过汇编lock前缀指令触发底层锁的机制
锁的机制两种:总线锁/MESI缓存一致性协议
主要帮助我们解决多个不同cpu之间缓存之间数据同步

什么是总线:
cpu和内存进行交互就得通过总线

总线锁
1.最初实现就是通过总线加锁的方式也就是上面的lock与unlock操作,但是这种方式存在很大的弊端。会将我们的并行转换为串行,从而失去了多线程的意义
2.当一个cpu(线程)访问到我们主内存中的数据时候,往总线总发出一个Lock锁的信号,其他的线程不能够对该主内存做任何操作,变为阻塞状态。该模式,存在非常大的缺陷,就是将并行的程序,变为串行,没有真正发挥出cpu多核的好处。

Intel- 64 与 IA-32架构软件开发者手册对lock指令的解释:

  1. 底层实现主要通过汇编lock前缀指令,它会锁定这块内存区域的缓存(缓存行锁定)并回写到主内存。
  2. 会将当前处理器缓存行的数据立即写回系统主内存;
  3. 这个写回主内存的操作会引起在其他CPU里缓存了该内存地址的数据无效(MESI协议)

8.1 Java汇编指令查看

首先需要将该工具 hsdis-amd64.dll

E:\java8\jdk\jre\bin\server 放入 hsdis-amd64.dll

-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*Demo01.*

注意:修改类的名称(Demo01为类的名称)

 0x0000000002ae6277: lock addl $0x0,(%rsp)     ;*putstatic FLAG
                                                ; - com.demo.Demo01::main@17 (line 24)

8.2 MESI协议实现的原理

总线:维护解决cpu高速缓存副本数据之间一致性问题。

  1. E 独享状态:在单核的情况下 ,只有一个线程 当前线程工作内存(主内存中副本数据)与主内存数据保持一致的则 当前cpu的状态:E 独享状态。
  2. S 表示为共享 在多个cpu的情况下 每个线程中工作内存中(主内存中副本数据)与主内存数据保持一致性,则当前cpu的状态是为:S 共享状态。
  3. M 修改 当前线程线程修改了工作内存中的数据,当前cpu的副本数据与主内存数据不一致性的情况下,则当前cpu的状态为M状态。
  4. I 无效 总线嗅探机制 如果发现cpu中副本数据与主内存数据不一致的情况下,则会认为无效需要从新刷新主内存中的数据到工作内存中。

8.3 为什么Volatile不能保证原子性

Volatile 可以保证可见性(保证每个cpu中的高速缓存数据一致性问题)
但是不能够保证数据原子性
禁止重排序

public class VolatileAtomThread extends Thread {

    private static volatile int count;

    public static void create() {
        count++;
    }

    public static void main(String[] args) {
        ArrayList<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Thread tempThread = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    create();
                }
            });
            threads.add(tempThread);
            tempThread.start();
        }
        threads.forEach(thread -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(count);
    }
}

Volatile为了能够保证数据的可见性,但是不能够保证原子性,及时的将工作内存的数据刷新主内存中,导致其他的工作内存的数据变为无效状态,其他工作内存做的count++操作等于就是无效丢失了,这是为什么我们加上Volatile count结果在小于10000以内。

8.4 为什么System.out.println 保证线程的可见性

println底层代码中使用了Synchronized, Synchronized可以保证线程可见性和线程安全性

1、线程解锁前,必须把共享变量的最新值刷新到主内存中;
2、线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量是需要从主内存中重新读取最新的值(加锁与解锁需要统一把锁)

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超级码里喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值