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

并发-4-volatile

内存模型的相关概念:

程序的执行过程为:主存->复制到cache(CPU的高速缓存)->CPU->刷新cache->回写到主存中 共享变量:被多个线程同时访问的变量 特殊情况:主存中i=0,程序A和B分别读i=0至核心1和核心2(多核CPU)的cache,进行i=i+1,则对这个共享变量虽然操作了两次,但是最后写回主存还是i=1。具体过程如下:

    |-------主存-------|----核心1的cache----|----核心2的cache----|

    |-------i=0-------|-----i=0-----------|------i=0-----------|

    |-------i=0-------|-----i=1-----------|------i=1-----------|

    |-------i=1-------|-----i=1-----------|------i=1-----------|
复制代码

这就是著名的缓存一致性问题 问题的解决方案MESI协议:当CPU写数据时,如果发现这个变量是共享变量,则通知其他的CPU设置该共享变量的缓存行为无效,当其他CPU需要读取这个变量时,发现自己的缓存中缓存该变量的缓存行是无效的,它就会从内存中读取

原子性:

要么全部执行,要么全部不执行,不会被打断。
eg.对一个32位的int型变量赋值:
eg.i=3分为两步:1.对低16位赋值。2.对高16位赋值。这时就必须要原子性操作,要么全部成功,要么全部不成功
复制代码

可见性:

多个线程访问同一个变量的时候,一个线程修改了值,另一个线程能立即看到修改的值
eg.线程1:int i = 0;    i=10,    线程2:j=i。
CPU1执行线程1,CPU2执行线程2
当线程1将i=0读到cache中并且设置为10(线程修改了值),但是未写入主存,线程2执行j=i时还是取出主存中i=0的值(未看到最新的值)
复制代码

有序性:

以下指令可能会发生指令重排序

语句1:int a=10;

语句2:int r=2;

语句3:a=a+3;

语句4:r=a*a;

考虑数据依赖性之后的指令执行顺序可能是2->1->3->4
复制代码

多线程下的有序性问题:

线程1:(语句1)context = loadContext();   

      (语句2)intited=true;

线程2:(语句1)while(!inited){

           sleep()

       }

      (语句2)doSomethingwithconfig(context)

因为线程1的两个语句之间没有依赖,所以可能发生不恰当的指令重排列:如下:

线程1先执行了语句2,导致线程2的intied误认为初始化完成,线程2跳过语句1,进行了语句2的doSomethingwithconfig(context)工作

由此可见,指令重排列不会影响当个线程的执行,但会影响到程序并发的执行
复制代码

java内存模型:

在JVM规范中,试图定义了一种Java内存模型,(java memeory model,JMM)来保证各个平台下的内存访问差异,以实现个平台下的JVM都能达到一致的访问内存的效果,但是JVM没有限制CPU或者寄存器,更没有禁止指令重排列,所以也会存在一致性问题。

Java内存模型规定所有的变量都是存在主存当中(类似于物理内存),每个线程都有自己的工作内存(类似于高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。

原子性:java内存模型只对基本数据类型的读取和赋值(int i = 0)保证了原子性,剩下的需要由synchronized和Lock来实现

可见性:volatile修饰的变量,一旦发生修改,就会更新主存,synchronized和Lock一样可以保证可见性

synchronized和Lock可以保证在释放锁之前将会对变量的修改刷新到主存中

案例分析:

volatile的意义:

    1.保证了不同线程对这个变量进行操作的可见性    

    2.禁止指令重排序
复制代码
//线程1
boolean isStop = false;
while(!stop){
   doSomething()
}
//线程2
stop = true;
复制代码
每个线程都有自己的工作内存,每个线程对变量的操作都要在工作内存中进行,不能直接对主存进行操作,线程之间的工作内存相互隔离

线程1将isStop读取到自己的工作内存,如果线程2在自己的工作内存中对isStop进行了修改,但是线程1还是没有进行刷新,所以会一直运行下去

对线程1的isStop加上volatile之后就保证了可见性:

1.当线程2修改isStop时,会做两件事,一就是对isStop立即更新到主存,二就是置线程1的缓存行为无效

2.当线程1再次读取isStop时,发现自己的缓存行无效,就会去读主存最新的值

volatile可以保证共享变量的可见性
复制代码

案例分析2:

public class ThreadVolatile {
    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(String[] args) {
        final ThreadVolatile test = new ThreadVolatile();

        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    test.increase();
                }
            }).start();
        }

        while (Thread.activeCount() > 1) {
            //保证前面的线程都执行完
            Thread.yield();
        }
        out.println("inc=" + test.inc);
    }
}
复制代码

输出:

inc=999452
复制代码
因为自增操作不是原子性的,所以虽然保证了可见性,但还是不够,每次操作的数都会小于10000

eg.假设i=10此时,线程1取出i=10,还未进行(i=i+1,写入主存)这两个操作时就阻塞了

   线程2取出i=10,完成了i=i+1,写入了主存i=11

   线程1执行i=i+1,写入主存i=11;

volatile不可以保证原子性

使用AtomicInteger,是JDK中新增的一种利用CAS锁原理实现的基本数据类型的原子操作

参考下面的代码:
复制代码
public class ThreadVolatile {
    public AtomicInteger anInt = new AtomicInteger(0);

    public void increase() {
        anInt.incrementAndGet();
    }

    public static void main(String[] args) {
        final ThreadVolatile test = new ThreadVolatile();

        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    test.increase();
                }
            }).start();
        }

        while (Thread.activeCount() > 1) {
            //保证前面的线程都执行完
            Thread.yield();
        }
        out.println("inc=" + test.anInt.get());
    }
}
复制代码

输出:

inc=1000000
复制代码

有序性:

因为volatile不可以保证变量的原子性,但是可以保证变量的可见性,那么可以部分保证有序性,如下:
复制代码
//线程1:
context = loadContext();   //语句1
volatile inited = true;             //语句2

//线程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);
复制代码
这里如果用volatile关键字对inited变量进行修饰,就不会出现这种问题了,因为当执行到语句2时,必定能保证context已经初始化完毕。
复制代码

使用场景:

具备条件:

1.对变量的写操作不依赖于当前值

2.该变量没有包含在其他变量的不变式中

也就是说,必须保证操作是原子性操作(i++就不可以),才能保证volatile关键字的程序在并发的时候能够正确执行

例如: 状态标记量:

volatile boolean flag = false;
while(!flag){
    doSomething()
}
public void setFlag(){
    flag = true
}
复制代码

//使用于double check机制

class Singleton{
    private volatile static Singleton instance = null;
    private Singleton() {
    }
     
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}
复制代码

相关文章:

  • [SDOI2009]Elaxia的路线
  • ES学习笔记(12)--Symbol
  • Redis 中的布隆过滤器
  • json字符串 转换为数组
  • 用mpvue开发微信小程序
  • hadoop副本放置策略
  • 【逆序对】N*M Puzzle / Simple Puzzle
  • JavaCV cvEstimateRigidTransform函数使用心得
  • 10.17_T1 平津战役
  • EOS开发完全解析(二):用cleos命令行创建、导入、解锁钱包
  • 返回一个二维整数数组中最大子数组的和
  • 1、jeecg 笔记开篇
  • 论文笔记:Visual Semantic Navigation Using Scene Priors
  • InlineHookPsTerminateProcess(0环)
  • 人工智能会改变世界?那这项技能你必须要掌握了。
  • (ckeditor+ckfinder用法)Jquery,js获取ckeditor值
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • css系列之关于字体的事
  • exif信息对照
  • gitlab-ci配置详解(一)
  • IDEA 插件开发入门教程
  • Java程序员幽默爆笑锦集
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • vuex 笔记整理
  • 从地狱到天堂,Node 回调向 async/await 转变
  • 检测对象或数组
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • 微服务核心架构梳理
  • 源码安装memcached和php memcache扩展
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • ​比特币大跌的 2 个原因
  • ​香农与信息论三大定律
  • #AngularJS#$sce.trustAsResourceUrl
  • #FPGA(基础知识)
  • $.ajax()
  • (20)目标检测算法之YOLOv5计算预选框、详解anchor计算
  • (webRTC、RecordRTC):navigator.mediaDevices undefined
  • (多级缓存)多级缓存
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (转)chrome浏览器收藏夹(书签)的导出与导入
  • *** 2003
  • ./mysql.server: 没有那个文件或目录_Linux下安装MySQL出现“ls: /var/lib/mysql/*.pid: 没有那个文件或目录”...
  • .NET Core引入性能分析引导优化
  • .NET Standard 支持的 .NET Framework 和 .NET Core
  • .net 逐行读取大文本文件_如何使用 Java 灵活读取 Excel 内容 ?
  • .net6使用Sejil可视化日志
  • .NET上SQLite的连接
  • .NET设计模式(11):组合模式(Composite Pattern)
  • .pop ----remove 删除