并发编程------volatile原理分析(一)

想要了解volatile之前,先去回顾一下多线程,多线程存在的意义?提高我们Http协议响应效率,能够对用户有一个比较好的体验。或者说提高程序的效率。那么怎么理解多核多线程。

多核多线程
如果是单核的CPU(默认情况下,一核两个线程)情况下,cpu在同一个时刻只能执行一个线程,所以存在切换过程,并不是真正的多线程,所以多核多线程需要的是多核cpu,少量的cpu切换

多线程的五个状态

  1. 创建
  2. 就绪
  3. 阻塞
  4. 运行
  5. 死亡

volatile原理分析

什么是 Volatile
能够保证线程可见性,当一个线程修改共享变量时,能够保证对另外一个线程可见性,但是注意不能够保证共享变量的原子性问题

Volatile的特性

  1. 可见性
    能够保证线程可见性,当一个线程修改共享变量时,能够保证对另外一个线程可见性,但是注意他不能够保证共享变量的原子性问题。
    对于可见性要怎么理解呢?
    可以通过代码来看。
/**
 * @author 龙小虬
 * @date 2021/4/20 19:39
 */
public class Main extends Thread{

    private static boolean flag = true;

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

        }
    }

    public static void main(String[] args) {
        new Main().start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = false;
        System.out.println("主线程已经停止....");
    }
}

上面这个代码在主线程中开了一个线程去运行重写的run()方法。
看看运行结果:
在这里插入图片描述
可以看到主线程已经停止了,但是主线程开的另一个线程却还在运行。那为什么会这样呢?这和我们平时理解的完全不同啊,flag不是被改变了吗?为什么还在运行?
其实这是因为在每个cpu中都有自己独立的存取数据的地方,而他们的数据存取是各自的高速缓存区,为什么要去使用高速缓存区?
假如不需要高速缓存区,那么我们每一次数据的存取都需要通过主内存去获取,那么会导致读取数据比较慢。就类似于我们使用mysql数据库的时候,我们会使用redis缓存或者jvm内置缓存一个道理,就是为了保证数据读取的效率问题。
上面的这个问题,我们只需要保证全局变量的可见性就能使全部线程结束,也就是在全局变量中添加volatile关键字。

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

既然看到了线程的不可见的问题,就来了解一下JMM内存模型。
在这里插入图片描述
JMM八大同步规范:

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

这里面有一个总线,那他的作用是什么呢?

总线
总线:主要解决多个cpu高速缓存副本之间数据的一致性问题
实现原理:

  1. 总线锁的形式
    效率比较低
    因为只要一个线程操作主内存,那么其他线程就不允许操作主内存。当我们一个cpu线程访问到主内存数据的时候,往总线中发出一个lock锁指令,其他的cpu线程无法对我们的主内存做任何操作,这样的机制就会无法发挥多核的运行效率
  2. MESI形式
    M(修改)
    如果当前cpu副本数据如果和主内存的数据不一致的情况下,则当前cpu状态为修改
    E(独占、互斥)
    当只有一个cpu线程的情况下,cpu副本数据与主内存数据如果保存一致的情况下,则该cpu状态独享
    S(共享)
    在多个cpu线程的情况下,每个cpu副本之间数据如果保持一致的情况下,则当前cpu状态为共享
    I(无效)
    多个cpu副本数据不一致

既然了解了总线的实现原理,那么就来了解一下volatile实现原理,前面提到过总线是为了解决多个副本数据一致性问题,那么volatile底层就是一把lock锁,保证一致性问题,在很早的时候,是使用总线锁来实现,但是最后总线锁无法真正实现多核多线程的意义,最后使用到了MESI缓存一致性协议。这个MESI一致性协议主要就是当一个副本数据被更改,那么状态则会变为M,其他的副本数据状态变为I,那么为I的副本数据则会重新进入主内存中读取数据。

那我们怎么知道使用了lock锁呢?
我们使用一个工具来实现。

Java汇编指令查看lock指令

将hsdis-amd64.dll文件放在jdk\jre\bin\server文件夹下。注意:是jdk下的jre文件夹
在VM options:中加入-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*Main.*(这里的Main为java文件名)即可打印出信息。可以看到

在这里插入图片描述
在这里插入图片描述
都是有lock锁的。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

龙小虬

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

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

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

打赏作者

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

抵扣说明:

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

余额充值