volatile
是用来标记一个JAVA变量存储在主内存(main memory)
中,多线程读写volatile变量会先从高速缓存中读取,可是写入的时候会当即经过内存总线刷到主存,同时内存总线中会对这个变量进行监听,当发现数据变更时,会主动将该变量的CPU Cache置为失效。确切的说:每次写操做volatile变量
时,将直接将主内存(main memory)
中最新的值读取到当前Cache
操做html
<!-- more -->java
可见性: 是指线程之间数据可见共享,一个线程修改的状态对另外一个线程是可见的。好比:用volatile修饰的变量
,就会确保变量在修改时,其它线程是可见的。。git
在多线程中,对非volatile变量
进行操做的时候,出于对性能的考虑,当对这些变量进行数据操做时,线程可能会从主内存里拷贝变量到CPU Cache中去。多核CPU环境中,多个线程分别在不一样的CPU中运行,就意味着,多个线程都有可能将变量拷贝到当前运行的CPU Cache里。缓存
以下图所示(多线程数据模型): 微信
public class NotSharedObject { private static int COUNTER = 0; private static final int MAX_LIMIT = 5; public static void main(String[] args) { new Thread(() -> { int localValue = COUNTER; while (localValue < MAX_LIMIT) { if (localValue != COUNTER) { System.out.printf("[线程] - [%s] - [%d]\n", Thread.currentThread().getName(), COUNTER); localValue = COUNTER; } } }, "READER").start(); new Thread(() -> { int localValue = COUNTER; while (COUNTER < MAX_LIMIT) { System.out.printf("[线程] - [%s] - [%d]\n", Thread.currentThread().getName(), ++localValue); COUNTER = localValue; } }, "UPDATER").start(); } }
[线程] - [UPDATER] - [1] [线程] - [UPDATER] - [2] [线程] - [UPDATER] - [3] [线程] - [UPDATER] - [4] [线程] - [UPDATER] - [5]
结果代表,UPDATE
线程虽修改数据,可是READER
线程并未监听到数据的变更,当前线程操做的是当前CPU Cache
里的数据,而不是从main memory
获取的。多线程
couner 的变量未使用volatile关键字修饰
,即JVM没法保证有效的将CPU Cache
的内容写入主存中。意味着 counter 变量在CPU Cache
中的值可能会与主存中的值不同。app
以下图所示(无Volatile): 性能
private static volatile int COUNTER = 0; [线程] - [UPDATER] - [1] [线程] - [UPDATER] - [2] [线程] - [READER] - [1] [线程] - [UPDATER] - [3] [线程] - [UPDATER] - [4] [线程] - [UPDATER] - [5] [线程] - [READER] - [3]
结果代表,volatile修饰后
的变量并不会达到Lock
的效果,它只会保证线程可见性,但不保证原子性
,在读取volatile变量
和写入它的新值时,因为操做耗时较短,就会产生 竞争条件:多个线程可能会读取到volatile变量的相同值,而后产生新值并写入主内存,这样将会覆盖互相的值。(有兴趣的能够在建立一个UPDATE
线程测试效果)测试
以下图所示(Volatile): 优化
从Java5
以后volatile关键字
不只能用于保证变量从主存中进行读写操做,同时还遵循Happens-Before原则
,下文将会描述存在于volatile
中的一些细节,想深刻的能够自行谷歌 happens-before relationship
或者访问提供的几个连接
参考文献(1):https://en.wikipedia.org/wiki/Happened-before
参考文献(2):http://preshing.com/20130702/the-happens-before-relation/
参考文献(3):http://www.importnew.com/17149.html
若是T1线程
写入了一个volatile变量
而后T2线程
读取该变量,那么T1线程
写以前对其可见的全部变量,T2线程
读取该volatile
以后也会对其可见。
禁止JVM指令重排优化,一旦被volatile
修饰的变量,赋值后多执行了一个load addl $0x0, (%esp)
操做,至关于多了一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障以前的位置),单核CPU访问内存时,并不须要内存屏障;
看看下面这个示例:
T1线程: Object obj; volatile boolean init; ---------T1线程------------ obj = createObj() 1; init = true; 2; ---------T2线程------------ while(!init){ sleep(); } useTheObj(obj);
被volatile修饰过的变量 init
在写操做以前,建立了非volatile变量的obj
,于是T1线程
在写入init
后,会将obj
也写入主内存中去。
因为T2线程
启动的时候读取被volatile修饰过的init,于是变量 init
和变量 obj
都会被写入T2线程
所使用的CPU缓存中去。当T2线程
读取 obj
变量时,它将能看见被T1线程
写入的东西。
线程可见,状态量标记
volatile boolean start = true; while(start){ // } void close(){ start = false; }
屏障先后一致性,禁止指令重排
我的QQ:1837307557
battcn开源群(适合新手):391619659
微信公众号:battcn
(欢迎调戏)