Java并发——volatile详解+JMM内存模型

https://www.jianshu.com/p/8755e8fd1d20
1.【volatile】
1.1 volatile的特性

  • 可见性 : 对一个volatile的变量的读,总是能看到任意线程对这个变量最后的写入.

    并不能保证原子性 :
    对于单个volatile变量的读或者写具有原子性,但是并不能解决并发问题。volatile仅仅保证了最新的修改能被每一个线程知道但是不能保证每一次只有一个线程来修改值。即并不能保证原子性。想保证原子性还是需要以来一些加锁的操作,比如用sychronized修饰累加的方法等

  • 禁止进行指令重排序

1.2 volatile的内存语义

  • volatile的写和锁的释放具有相同的语义,当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存.(也就是说所有对volatile变量的写操作,一定会同步刷新主内存)
  • volatile的读和锁的获取有相同的语义,当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。(所有对volatile变量的读操作,一定是保证从主内存中读取,由于主内存始终存的是最新的,从而保证了可见性)

2.既然前面说了volatile保证了可见性,那不得不提到java中是如何读取内存变量中的数据值的。
https://www.cnblogs.com/null-qige/p/9481900.html——参考链接
这里简要说一下,java中操作变量,都是操作其线程对应的cache中的数据,并不是直接操作主内存。
所以这里不得不补充说明,缓存和内存的一致性问题:在学习计算机组成原理时,我们知道,修改了缓存的数据后,有如下3中写回策略:
(1)写回法

当CPU写Cache命中时,只修改Cache的内容,而不立即写入主存;只有当此行被替换出时才写回主存。

优点:减少了访问主存的次数。

缺点:存在不一致性的隐患。

解决问题:每个Cache行必须配置一个修改位,以反映此行是否被CPU修改过。

(2)全写法

当CPU写Cache命中时,Cache与主存同时发生写修改。因而较好地维护了Cache与主存内容的一致性。当CPU写Cache未命中时,直接向主存进行写入。

优点:Cache中每行无需设置一个修改位以及相应的判断逻辑。

缺点:cache对CPU向主存的写操作无高速缓冲功能,降低了Cache的功效。

(3)写一次法

基于写回法并结合全写法的写策略,写命中与写未命中的处理方法与写回法基本相同,只是第一次写命中时要同时写入主存(全写法)。

优点:便于维护系统全部Cache的一致性。

3.【缓存一致性协议】
鉴于上面写回法的缓存一致性问题,于是多核cpu在硬件设计上实现了缓存一致性协议。cache coherence对程序员来说是透明的,你可以完全不关心它的存在;它所解决的问题,是一个内存里的变量,被多个cache所加载之后,一个CPU core去修改了这个变量值,如何传播到其他cpu的cache上。也就是说,有了cache coherence之后,你可以放心地在一个core里修改变量,它早晚会被另一个core读到。
从上面可以看出,这和分布式系统中的最终一致性类似,它一定会在最终某个时刻使多个cpu缓存达成一致状态。
尽管我们不用考虑缓存的最终一致性问题,但是他仍然不能避免并发操作带来的错误,对于连续一致性要求的场景,我们必须要实现多线程同步。

4.【JMM三大特征】

  • 可见性 (Visibility):可见性是指当一个线程在修改一个变量之后,其他的线程能够立即得到这个修改后的新值。
  • 原子性 (Atomicity):由Java内存模型来直接保证的原子性操作的read、load、assign、use、store和write。所谓的原子性就是再一个操作或者一系列操作中,要不全部完成,要么全部失败。(Java内存模型中提到我们大致可以认为基本数据类型的访问读写是具备原子性的。【其中例外long和double的非原子性协定。但这种情况几乎不会发生。】)
  • 有序性 (Oredering):Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另外一个线程,那么所有的操作都是无序的(线程间不知道谁的指令会先执行)。

5.【总结】:
​ volatile看起来非常的简单,并且轻巧。但是也存在很多弊端。相较于synchronized来说,在某些场景可以代替synchronized,但又不能完全取代。因为在使用volatile时,大部分只能修饰关键变量,并且如果大量使用volatile反而会影响运行效率。因为volatile会禁止指令重排序,禁用CPU优化。在使用它必须满足如下两个条件:

  • 对变量的写操作不依赖当前值;
  • 该变量没有包含在具有其他变量的不变式中;
  • volatile经常用于两个场景:状态标记量、double check
发布了69 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/JustKian/article/details/103734714