volatile关键字是由JVM提供的最轻量级同步机制。保证可见性、有序性
Java内存模型:
Java内存模型由Java虚拟机规范定义,用来屏蔽各个平台的硬件差异。简单来说:所有变量储存在主内存。
每条线程拥有自己的工作内存,其中保存了主内存中线程使用到的变量的副本。
线程不能直接读写主内存中的变量,所有操作均在工作内存中完成。
对被volatile修饰的变量进行操作时,需要满足:
a:线程对变量执行的前一个动作是load时才能执行use,反之只有后一个动作是use时才能执行load。线程对变量的read,load,use动作关联,必须连续一起出现。-----这保证了线程每次使用变量时都需要从主存拿到最新的值,保证了其他线程修改的变量本线程能看到。
b:线程对变量执行的前一个动作是assign时才能执行store,反之只有后一个动作是store时才能执行assign。线程对变量的assign,store,write动作关联,必须连续一起出现。-----这保证了线程每次修改变量后都会立即同步回主内存,保证了本线程修改的变量其他线程能看到。
c:有线程T,变量V、变量W。假设动作A是T对V的use或assign动作,P是根据规则2、3与A关联的read或write动作;动作B是T对W的use或assign动作,Q是根据规则2、3与B关联的read或write动作。如果A先与B,那么P先与Q。------这保证了volatile修饰的变量不会被指令重排序优化,代码的执行顺序与程序的顺序相同。
锁
volatile使用
- volatile修饰变量
- 保证可见性、有序性
volatile特征
- 1)保存内存可见性
- volatile修饰的变量不会缓存到工作内存中,每一次读取获取最新volatile变量
- (所有线程的共享变量都存储在主内存中,每一个线程都有一个独有的工作内存,每个线程不直接操作在主内存中的变量,而是将主内存上变量的副本放进自己的工作内存中,只操作工作内存中的数据。当修改完毕后,再把修改后的结果放回到主内存中。每个线程都只操作自己工作内存中的变量,无法直接访问对方工作内存中的变量,线程间变量值的传递需要通过主内存来完成。)
- 2)禁止指令重排序
- Java内存不会对volatile指令进行重排序,从而保证对volatile的执行顺序永远是按照书写顺序执行的
原理:
volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。
在JVM底层volatile是采用“内存屏障”来实现的。
观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
a)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
b)它会强制将对缓存的修改操作立即写入主存;
c)如果是写操作,它会导致其他CPU中对应的缓存行无效。
volatile与synchronized的区别
(1)使用上的区别
Volatile只能修饰变量,synchronized只能修饰方法和语句块
(2)对原子性的保证
synchronized可以保证原子性,Volatile不能保证原子性
(3)对可见性的保证
都可以保证可见性,但实现原理不同
Volatile对变量加了lock,synchronized使用monitorEnter和monitorexit monitor JVM
(4)对有序性的保证
Volatile能保证有序,synchronized可以保证有序性,但是代价(重量级)并发退化到串行
(5)其他
synchronized引起阻塞
Volatile不会引起阻塞