多线程(7)- volatile关键字

本文详细探讨了Java中的volatile关键字,展示了在多线程环境下如何解决缓存一致性问题。通过实例代码,解释了未使用volatile时可能出现的并发问题,并对比了使用volatile后的效果。同时,介绍了CPU缓存模型、缓存一致性协议(如MESI协议)以及Java内存模型(JMM)如何确保内存可见性。
摘要由CSDN通过智能技术生成


前言

volatile关键字
扩展:

概念

1、初识volatile

  • volatile关键字只能修饰类变量和实例变量、对于方法参数、局部变量、以及实例常量、类常量都不能进行修饰。

  • 一段代码

public class VolatileFoo {

    private static final int MAX = 5;
    private static       int x   = 0;

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            int local_value = x;
            while (local_value < MAX) {
                if (local_value != x) {
                    System.out.println(Thread.currentThread().getName()+" local_value =" + local_value + " x = " + x);
                    local_value = x;
                }
            }
        }, "reader").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            while (x < MAX) {
                System.out.println(Thread.currentThread().getName()+" x change = " + ++x);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "writer").start();

    }
}

输出结果:
writer x change = 1
writer x change = 2
writer x change = 3
writer x change = 4
writer x change = 5

  • 修改类变量x为volatile修饰
private volatile static       int x   = 0;

输出结果:
writer x change = 1
reader local_value =0 x = 1
writer x change = 2
reader local_value =1 x = 2
writer x change = 3
reader local_value =2 x = 3
writer x change = 4
reader local_value =3 x = 4
writer x change = 5
reader local_value =4 x = 5

Process finished with exit code 0

2、CPU Cache模型

  • 计算机中所有运算是CPU的寄存器完成的,CPU指令执行过程中涉及数据的读和写操作,访问的地方只能是计算机主存(RAM)

  • CPU发展频率不断提升,主存由于制造工艺及成本限制,速度没有很大突破,CPU与主存速度差距在上千倍,极端差距在上万倍

  • 基于上述原因,现在在CPU和主存之间增加缓存,目前最高级别是三级缓存,最靠近PCU的缓存称为L1,然后依次是L2,L3和主内存。

  • 由于程序指令和程序数据的行为和热点分步差异很大,因此L1又被划分为L1i(instruction)和L1d(data)这两种专门用途的缓存。

  • Cache Line可以认为是CPU Cache中的最小缓存单位、目前主流CPU Cache的Cache Line大小都为64字节。
    在这里插入图片描述

CPU Cache交互

-

2、CPU缓存一致性问题

  • 由于CPU Cache的出现,极大提高了CPU的吞吐能力,但也产生了缓存缓存不一致问题。

  • 比如i++ 操作,在程序运行过程中,首先需要将主内存中的数据复制一份到CPU Cache中,那么CPU寄存器在进行数值计算的时候就直接到Cache中读取和写入,整个过程结束之后再将Cache中的数据刷新到主存当中,过程如下:

    1. 读取主内存的i到CPU Cache中
    2. 对i进行+1操作
    3. 将结果写回到Cache中
    4. 将数据刷新到主存中
  • 在单线程中不会有问题,但是在多线程情况下,每个线程都有自己的工作内存(本地内存,对应CPU中的Cache),变量i会在多个线程的本地内存中都存在一个副本,如果同时有两个线程进行i++操作,假设i的初始值=0、每个线程都从主存中获取i的值存入CPU Cache中,然后经过计算再写入主内存,可能i经过两次i++的结果还是1这就是典型的缓存不一致问题。

  • 解决方式:

    • 通过总线加锁的方式
    • 通过缓存一致性协议
      在这里插入图片描述

  • 缓存一致性协议:Intel的MESI协议
    • 读取操作;不做任何处理、只是将Cache中的数据读取到寄存器
    • 写入操作;发出信号通知其他CPU将该变量的Cache line置为无效状态,其他CPU在进行该变量读取的时候不得不到主内存中再次获取。

4、Java内存模型JMM(Java Memory Mode)

  • JVM采用内存模型机制屏蔽各个平台和操作系统之间内存访问的差异,以实现让Java程序在各个平台达到一致的内存访问效果.比如C语言中的整形变量,在某些平台占用两个字节,某些占用四个字节,Java则是在任何平台下,int类型就是四个字节,这就是所谓一致内存访问效果。
    在这里插入图片描述
  • 共享变量存储于主内存中,每个线程都可以访问
  • 每个线程都有私有的工作内存或者称为本地内存
  • 工作内存只存储该线程对共享变量的副本
  • 线程不能直接操作主内存,只有先操作了工作内存之后才能写入主内存
  • 工作内存和Java内存模型一样也是一个抽象的概念,它其实并不存在,它涵盖了缓存,寄存器,编译器优化以及硬件等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值