1 volatile是什么?
它是Java提供的一种轻量级的同步机制。与synchronized修饰方法,代码块不同,volatile只用来修饰变量。并且与synchronized,Lock等重量级锁不同的是,volatile更轻量级,因为它不会引起线程上下文的切换和调度。
2 volatile作用
我们知道并发编程的三大特性:原子性,可见性,有序性。
原子性:
即一个或者多个操作作为一个整体,要么全部执行,要么都不执行,并且操作在执行过程中不会被线程调度机制打断;而这种操作一旦开始,就一直运行到结束,中间不会有任何上下文切换。
可见性:
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:
为了提高程序的执行效率,编译器对编译后的指令进行重排序,即代码的编写顺序不一定就是代码的执行顺序。
并发编程只有同时满足这三大特性,才能保证程序正确的执行,而volatile只保证了可见性和有序性,不保证原子性。
volatile的作用只有两个:
2.1 保存内存的可见性
2.2 禁止JVM内存重排序(保证有序性)
在并发多线程情况下,为什么会有可见性问题?如果不做控制,为什么一个线程修改了共享变量的值,其他线程不能立即看到。这里就需要了解JMM(JAVA内存模型,JAVA memory model)
由于JAVA共享变量是存储在主内存中,而JAVA线程是无法直接访问主内存数据,只能把主内存的数据拷贝一份副本,修改完本地内存的数据,再写回主内存,而此时另一个线程也把主内存的数据拷贝到自己私有的本地内存中,虽然线程1已经修改了主内存数据,但线程2却无法感知到,所以就出现了内存可见性问题。
3 可见性实现原理:
当一个共享变量声明为volatile后,会有以下效果:
3.1 当写一个volatile变量时,JMM会把该线程对应的本地内存中的变量强制刷新到主内存中去。
3.2 这个写回操作会导致其他线程的缓存无效。
(volatile主要通过汇编lock前缀指令,它会锁定当前内存区域的缓存行,并且立即将当前缓存行数据写入到主内存中耗时非常短),回写主内存的时候会通过MESI协议使其他线程缓存了该变量的地址失效,从而导致其他线程需要去主内存中重新读取数据到工作线程中。)
有序性保证的原理:
它是通过插入内存屏障,在内存屏障前后禁止重排序优化,以此实现有序性。
4 volatile应用场景:
它可以保证可见性和有序性,但无法保证原子性,所以它的应用场景不如synchronized广泛,主要有两个场景:一个是做状态变量,二是做需要重新赋值的共享对象。
状态变量 这是在java项目中执行的,如果去掉volatile修饰的话,那么就不会打印Thread1接受到了修改的值flag这句话。
但是如果放在安卓demo中,即使不用vloatile也能通知其他线程(没搞明白为什么具体再实践下尝试下)。
5 vloatile与synchronized的区别:
volatile只能修饰变量,而后者可以修饰方法,语句块。
vllatile不能保证原子性,而后者是可以保证原子性的。
都可以保证可见性,但原理不同,Volatile是对变量加了Lock,而后者使用monitorEnter和monitorExit
volatile不会引起阻塞,而后者会。
在一些场景下使用volatile性能是要更好地。
6 volatile使用条件:
对变量的写操作不依赖当前值:比如i++操作,变量的写操作依赖安全值,所以不能保证线程安全。
该变量没有包含在具有其他变量的不变式中。比如i<value,即使i变量声明为volatile,也不能保证线程安全,因为value可能在运行时候的判断发生变化。