【并发编程】volatile

volatile的作用

1、保证变量可见性

说到volatile,就不得不提一个词:“可见性”,可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

要具体理解这句话的含义,还需要看下Java的内存模型:

java内存模型

由于内存和cpu之间的计算速度差距过大,如果cpu直接从内存中读取数据十分影响性能,所以在cpu和内存之间加了一个沟通的桥梁——高速缓存。

Java内存模型规定:

1.所有的变量都存储在主内存中;

2.线程拥有自己的工作内存(即高速缓存),线程的工作内存中保存了该线程所使用到的变量(拷贝自主内存);

3.线程对变量的所有操作都必须在工作内存中进行,不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

单线程情况下,不会产生任何问题,但当多线程访问同一变量时,就有可能读到脏数据。

假设有一个成员变量i,初值为0,线程A和线程B同时修改一个变量i,首先,线程A、B将变量i从主存读到工作内存中,初值均为0,线程A将i+10,线程B将i+1,此时,由于线程A可能还没有将工作内存中的数据(i=10)刷新到主存,B没有重新拷贝主存中的数据,导致B线程看到的i仍然为0,也就是说,A线程修改的变量对B线程“不可见”了,即出现“脏读”。

此时,如果对成员变量i加volatile关键字,同样是上述场景,i在每次被线程访问时,都检查变量的地址是否改变,如改变,就强迫从主内存中重读该成员变量的值,并且,当成员变量发生变化时,强迫线程将变化值回写到主内存中,这样在任何时刻,AB线程总是看到某个成员变量的同一个值。

2、禁止指令重排序

1.当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;并且在其后面的操作肯定还没有进行。

2.在进行指令优化时,不能将在对volatile变量的读操作或者写操作的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

在Java内存模型中,为了保证效率,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

例如我们常见的双重检查单例:

双重检查单例

为什么要给instance加上volatile关键字呢?这是由于instance=new Singleton();这句话不是原子操作,在JVM中,这句话被分为三个阶段:

1.为instance分配内存

2.初始化instance

3.将instance变量指向分配的内存空间

由于JVM重排序的存在,上述23两步操作是没有依赖关系的,可能被重排序,也就是说,在instance还没被初始化的时候,instance就已经不为null了,这时,另一个线程执行到第一个if,判断不为null,直接return instance,导致异常。

volatile的实现原理

1、可见性

对声明了volatile变量进行写操作时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据回写到系统内存, 这一步确保了如果有其他线程对声明了volatile变量进行修改,则立即更新主内存中数据。

但这时候其他处理器的缓存还是旧的,所以在多处理器环境下,为了保证各个处理器缓存一致,每个处理会通过嗅探在总线上传播的数据来检查自己的缓存是否过期,当处理器发现自己缓存行对应的内存地址被修改了,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作时,会强制重新从系统内存把数据读到处理器缓存里。 这一步确保了其他线程获得的声明了volatile变量都是从主内存中获取最新的。

2、有序性

Lock前缀指令实际上相当于一个内存屏障,它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。

volatile的使用条件

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

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

volatile的注意点

上面一条讲到了volatile关键字的使用条件,从注意点的角度来解释一下。

首先,volatile虽然可以用在并发编程中,但是不能代替synchronized关键字,因为volatile关键字只能保证并发编程中三大概念中的两个,即可见性和有序性,但是它不能保证程序的原子性,举例说明:

不能保证原子性的volatile

该例中,inc的结果为10000吗?不是,那问题出在哪里?

问题在于,increase方法中,inc++这个操作不具备原子性,线程要先读到位于主内存中的inc的值,然后对其进行+1,然后再将其放回主内存中,在这三步中,假设线程A读取了inc,正在进行+1操作,此时,线程B读取inc,由于线程A没有操作完,所有并没有将+1后的操作写入主存,导致B读取的仍为旧值,最终导致程序的结果小于预期结果,解决方法:可以将increase方法加上synchronized关键字,保证其原子性。

总结

本篇大致讲解了volatile的用途以及实现原理,上文也说到,他不能代替synchronized关键字,在下篇文章中,就要讲到Java中的各种锁,看下Java的锁机制是如何保证效率的情况下,在多线程中安全运行的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容