对volatile关键字的总结:volatile用来保证该变量对所有线程的可见性(从主内存加载到工作线程的值是最新的),但不保证原子性。
码出高效这么说:
每个线程都有独占的内存区域(工作线程),如操作栈,本地变量表等。线程本地内存保存了引用变量在堆内存中的副本,线程对变量的所有操作都在本地内存区域中进行,执行结束后再同步到堆内存中去(线程公有的主内存)
当然JMM和JVM内存结构不是一个层面上的划分,JMM更多的是为了描述变量原子性,可见性,线程的工作内存某种程度上来说是涵盖了虚拟机栈帧和程序计数器,而主内存则涵盖了所有的变量实例,不管是成员变量还是局部变量。所以我们平时讨论原子性,可见性的时候还是专门拿JMM内存模型来讲,而不是用JVM内存布局。
我们可以画出JMM内存
其中read:将主内存中的变量值读取到工作内存中
load:将read读取到的值保存到工作内存中的变量副本当中
store:将变量副本的值存储到主内存中
write:将store存储的值写入到主内存的共享变量中
而volatile关键字JMM在执行的时候会在最后加入一个内存屏障,内存屏障阻止了编译器的指令重排,告诉CPU和编译器,先于这个命令的必须先执行,后于这个命令的必须后执行。JMM会在write操作后加入一个写屏障,read操作前加入一个读屏障。
写屏障保证了一旦完成写入,任何访问主内存的线程,线程值都是最新的。
读屏障保证了,读取前,所有的写操作都将执行完成,保证读取到的值是最新的。
内存屏障/读写屏障保证了volatile关键字的可见性,同时保证了指令重排。因为为了实现内存屏障,JMM在编译器和处理器层面限制指令重排序。
volatile关键字为什么没有原子性?
从load到store到内存屏障到write之间,内存屏障固然保证了最新的变量值在所有线程可见,但从load到store是不安全的,这期间如果修改了值将产生丢失。
自增没有原子性。屏障,将主存值read到工作内存,load到变量副本,修改,store到主存,屏障,write。
改进:AtomicLong具有可见性和原子性。
volatile保证可见性,由于volatile仅进行读取和写入(set,get场景),并没有修改,因此引入了CAS的AtomicLong保证了可见性和原子性
volatile使用场景:一写多读。典型应用:CopyOnWriteArrayList(COW)。此外concurrentHashmap也有使用。