volatile关键字
volatile关键字的作用
1、保证数据在各个线程间的可见性
2、禁止指令重排序
依照我的理解,其实禁止指令重排序就是为了保证数据的可见性,保证缓存一致性,实现这两点都是使用了同一个方法就是加入了内存屏障
1、内存屏障的是如何保证数据的可见性的
详细参考:http://www.wowotech.net/kernel_synchronization/Why-Memory-Barriers.html
我简要概述上述链接的内容,上述链接讲的是CPU的缓存一致性和内存屏障,其实Java的内存模型和CPU的内存模型差不多,但是更加粗犷一些,如图,没有store buffer这些精细化的内存管理,只是给多个线程每个分配了一个工作内存(相当于高速缓存),图虽然是CPU的内存模型,但Java的也是如此,将CPU换成thread就行
内存屏障主要分为读屏障和写屏障
写内存屏障(Store Memory Barrier):处理器将当前存储缓存的值写回主存,以阻塞的方式。
读内存屏障(Load Memory Barrier):处理器处理失效队列,以阻塞的方式。
详细来说:
写内存屏障,将缓存中的脏数据flush到主存中,同时通过CPU总线告知其他CPU其缓存中的该项数据已经失效,不能再使用,其他CPU就将这条消息存储到失效队列中。
读内存屏障,线程去缓存中读取数据之前,会先处理失效队列,失效队列会将缓存中的一些值置为失效状态,处理完后再去读取缓存的数据,如果读取的数据刚好是失效的,线程就只能去主存中去去最新的值,这样就确保了数据的可见性。
在每个volatile写操作前插入StoreStore屏障,这样就能让其他线程修改A变量后,把修改的值对当前线程可见,在写操作后插入StoreLoad屏障,这样就能让其他线程获取A变量的时候,能够获取到已经被当前线程修改的值
在每个volatile读操作前插入LoadLoad屏障,这样就能让当前线程获取A变量的时候,保证其他线程也都能获取到相同的值,这样所有的线程读取的数据就一样了,在读操作后插入LoadStore屏障;这样就能让当前线程在其他线程修改A变量的值之前,获取到主内存里面A变量的的值
虽然Java中的内存屏障分为StoreStore,StoreLoad,LoadLoad,LoadStore四种,但其本质还是读,写这两种内存屏障,在volatile写操作之后加入store屏障,在volatile读操作之前加入load屏障,这样就可以确保数据对所有的线程可见。
2、禁止指令重排序
详细参见:http://www.wowotech.net/kernel_synchronization/453.html
内存屏障可以禁止指令重排序,主要在编译器阶段就禁止了其重排序,在编译器检测到内存屏障之后就不会对屏障前后的代码进行重排序,保证其执行顺序
这样就可以到达:
写volatile变量时,可以确保volatile写之前的操作不会被编译器重排序到volatile写之后。
读volatile变量时,可以确保volatile读之后的操作不会被编译器重排序到volatile读之前。