volatile的可见性和有序性以及对比syncronized

引言

面试京东的时候对这个知识点有点遗忘,现在进行一下总结。
解释为什么volatile可以保证可见性和有序性,回忆一下JMM的happens-before原则!以及为什么syncronized可以保证可见性和有序性和原子性!
同时推荐一篇JMM内存模型的文章https://blog.csdn.net/jinxinxin1314/article/details/106700414

JMM(Java内存模型)

在 JDK1.2 之前,Java 的内存模型实现总是从主存(即共享内存)读取变量,是不需要进⾏特别的注意的。
⽽在当前的 Java 内存模型下,线程可以把变量保存本地内存(⽐如机器的寄存器)中,⽽不是直接在主存中进⾏读写。这就可能造成⼀个线程在主存中修改了⼀个变量的值,⽽另外⼀个线程还继续使⽤它在寄存器中的变量值的拷⻉,造成数据的不⼀致。
在这里插入图片描述

主存是公共空间,基本可以类比为虚拟机模型中的堆,对象创建好了都是在主存里,所有线程都可以访问,工作内存是线程的私有内存,只有本线程可以访问,如果线程要操作主存中的某个对象,必须从主存中拷贝到工作内存,在对工作内存中的副本进行操作,操作后再写入主存,而不能对主存的对象直接操作

volatile的可见性

jvm知道这个变量是使用volatile修饰,那么他就会立即刷新到主内存中去,保证其他线程能够立即看到这个变量的修改,读取最新值。这也是volitile关键字的可见性。

举个例子:
第一:使用volatile关键字会强制将修改的值立即写入主存;

第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);

第三:由于线程1的工作内存中缓存变量无效,所以线程1再次读取变量的值时会去主存读取。

那么在线程2修改值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存变量的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。

那么线程1读取到的就是最新的正确的值。

volatile的有序性

volatile的有序性通过什么保证的?
答:JMM的happens-before规则,volatile可以禁止指令重排。

volatile有先天性的禁止重排序特性。同时: lock、Synchronized 都可以保证有序性

happens-before(先行发生原则) 差不多说一下前四条吧

1、写在上面的代码执行要在写在下面的代码前面

2、一个unLock操作先行发生于后面对同一个锁的lock操作 比如这样 lock unlock lock unlock 其中第一个unlock要早于 第二个lock操作执行。

3、对一个变量的写操作先行发生于后面对这个变量的读操作

4、如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C

5、Thread对象的start()方法先行发生于此线程的每个一个动作

6、对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生

7、线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行

8、一个对象的初始化完成先行发生于他的finalize()方法的开始

syncronized原子性

线程1在执行monitorenter指令的时候,会对Monitor进行加锁,加锁后其他线程无法获得锁,除非线程1主动解锁。即使在执行过程中,由于某种原因,比如CPU时间片用完,线程1放弃了CPU,但是它并没有进行解锁。而由于synchronized的锁是可重入的,下一个时间片还是只能被他自己获取到,还是会继续执行代码。直到所有代码执行完。这就保证了原子性。简而言之monitorenter加锁和monitorexit解锁后完成全部操作,中断不算。

syncronized有序性

程序执行的顺序按照代码的先后顺序执行。
在并发时,程序的执行可能会出现乱序。给人的直观感觉就是:写在前面的代码,会在后面执行。但是synchronized提供了有序性保证,这其实和as-if-serial语义有关。
as-if-serial语义是指不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果都不能被改变。编译器和处理器无论如何优化,都必须遵守as-if-serial语义。只要编译器和处理器都遵守了这个语义,那么就可以认为单线程程序是按照顺序执行的,由于synchronized修饰的代码,同一时间只能被同一线程访问。那么可以认为是单线程执行的。所以可以保证其有序性。
总结:
1.as-if-serial语义保证单线程有序性
2.synchronized排他锁,加锁后可以看作单线程运行
3.天然保证了有序性

syncronized可见性

被synchronized修饰的代码,在开始执行时会加锁,执行完成后会进行解锁,但在一个变量解锁之前,必须先把此变量同步回主存中,这样解锁后,后续其它线程就可以访问到被修改后的值,从而保证可见性。

关于syncronized的内容,推荐几篇文章
https://blog.csdn.net/qq_36270361/article/details/107708132
https://www.cnblogs.com/bcl88/p/12400747.html
http://blog.sina.com.cn/s/blog_c038e9930102v1ox.html

as-if-serial 和 happens-before 区别?

as-if-serial 保证单线程程序的执行结果不变,happens-before 保证正确同步的多线程程序的执行结果不变。

这两种语义的目的都是为了在不改变程序执行结果的前提下尽可能提高程序执行并行度。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值