== 原理之 volatile==*
volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)
- 对 volatile 变量的写指令之后会加入写屏障
- 对 volatile 变量的读指令之前会加入读屏障
1.如何保证可见性
-
写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
- 因为ready是volatile修饰的,所以下面对ready赋值true的操作之后,会添加一个写屏障,这个写屏障不仅仅将ready更改的数据写到主存中,也会将之前对共享变量的改动也写到主存中,即num=2,这个num最新的数据也会把它更新到主存中
-
public void actor2(I_Result r) { num = 2; ready = true; // ready 是 volatile 赋值带写屏障 // 写屏障(将以上对共享变量的修改都更新到主存中去) // (如果没加写屏障的话,这些共享变量的值只会先更新到当前线程的工作内存中) }
-
而**读屏障**(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
-
public void actor1(I_Result r) { // 读屏障 // ready 是 volatile 读取值带读屏障 if(ready) { r.r1 = num + num; } else { r.r1 = 1; } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HIWKerQZ-1612580875362)(assets/image-20210206103233291.png)]
2.如何保证有序性
-
写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
-
public void actor2(I_Result r) { num = 2; ready = true; // ready 是 volatile 赋值带写屏障 // 写屏障 }
- 这个写屏障是跟着ready=true这一句的,也就是说ready=true之前的这些代码不会重排序到ready=true之后去,但是ready=true之前的代码之间的有序性是没有保证的
-
读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
-
public void actor1(I_Result r) { // 读屏障 // ready 是 volatile 读取值带读屏障 if(ready) { r.r1 = num + num; } else { r.r1 = 1; } }
3.局限性
还是那句话,不能解决指令交错:
- 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去
- 写屏障强制将当前线程的工作内存中的数据更新到主存中去,但是这个写屏障不能保证另外一个线程是在当前这个线程把数据写到主存之后再去读,也就是说另外的这个线程它什么时候去读这个是控制不了的,另外的这个线程如果是在当前线程写之前,那它读到的就不是最新的数据;如果是在当前线程写之后,那它读到的就是最新的数据。这个是代码控制不了的,而是由任务调度器决定的。
- 而有序性的保证也只是保证了本线程内相关代码不被重排序
- 言外之意是:因为在单线程内,会发生指令重排序的问题,volatile只能保证当前线程的相关代码不被重排序(不重排序的规则看上面的读写屏障),但是如果是多线程,那么由操作系统的任务调度器决定,可能发生上下文切换,就有可能发生两个线程的指令交错执行。这两个线程的指令交错执行是volatile保证不了的。
总结下来就是:volatile只能解决可见性和有序性问题,但是不能解决原子性问题(多线程并发执行时,多个线程的指令交错执行的问题)。而synchronized能解决这三个问题。