博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
并发-4-volatile
阅读量:6232 次
发布时间:2019-06-22

本文共 4429 字,大约阅读时间需要 14 分钟。

内存模型的相关概念:

程序的执行过程为:主存->复制到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.禁止指令重排序复制代码
//线程1boolean isStop = false;while(!stop){   doSomething()}//线程2stop = 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复制代码
因为自增操作不是原子性的,所以虽然保证了可见性,但还是不够,每次操作的数都会小于10000eg.假设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();   //语句1volatile 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;    }}复制代码

转载地址:http://zkhna.baihongyu.com/

你可能感兴趣的文章
websocket搭建简单的网页聊天室框架【续1】
查看>>
Scrapy Shell
查看>>
array_merge和+号合并数组的区别
查看>>
TP5整合 WorkerMan 以及 GatewayWorker
查看>>
Facebook Docusaurus 中文文档 准备网站
查看>>
如何绘制一个圆圆的loading圈
查看>>
Nodejs学习记录:用koa.js开发微信公众号
查看>>
Android源码集锦,悬浮窗综合资讯类APP动画效果左右切换效果美妆领域
查看>>
Spring Cloud(六)服务网关 zuul 快速入门
查看>>
d3.js中动态数据的请求、处理及使用
查看>>
Vue源码解析(六)-vue-router
查看>>
[轮子系列]Google Guava之BloomFilter源码分析及基于Redis的重构
查看>>
android弹力效果菜单、组件化项目、电影票选座控件的源码
查看>>
three.js 中文文档 9.问答
查看>>
单元测试
查看>>
重温JS基础--JS中的对象属性
查看>>
慕课网_《RxJava与RxAndroid基础入门》学习总结
查看>>
CDH的hadoop与Spark套件组安装
查看>>
构建多层感知器神经网络对数字图片进行文本识别
查看>>
Git常规配置与基本用法
查看>>