只有满足了下面所有的标准后,才能使用volatile变量:
- 写入变量时并不依赖变量的当前值;或者能够确保只有单一的线程修改变量的值;
- 变量不需要与其他的状态变量共同参与不变约束;
- 而且,访问变量时,没有其他的原因需要加锁。
正确使用volatile变量的方式包括:用于确保它们所引用的对象状态的可见性,或者用于标识重要的生命周期时间(比如初始化或者关闭)的发生。
---------------------------------分割线-------------------------------------
java使用变量时,会先从主内存中将变量读取载入至工作内存,然后处理完以后,再存储写入工作内存。此时当多个内存读取并修改时就会发生并发。
而volatile可以确保变量的可见性以及指令有序性。但是volatile不能保证原子性。
为什么volatile不能保证原子性?它不是保证了每一时刻每个工作内存的被修饰变量一致吗?
比如 a++;
因为a++的执行等同于a = a + 1;
其中的执行流程分为:
- 从主内存读取a至工作内存;
- 将a与1相加;
- 将相加结果赋给a;
- 将a写回主内存。
此时当两个线程均执行至2、3步之间时,因为变量a未产生变化,因此对于各线程来说,a是一致的。此时某个线程执行了第4步,然后另外的线程已经执行完第2步,故这个回写的a值就会覆盖之前的a。
被volatile修饰的变量,其read、load、use这三个动作必定按顺序执行且中间不会有其他动作。assign、store、write同上面三个动作。
要是还不明白就看
https://blog.csdn.net/sureSand/article/details/82557274
volatile是如何保证可见性的呢?被volatile修饰的变量发生变化时,会将其他工作内存中的该变量全部无效化,使得其他线程需要重新读取载入变量,故保证了可见性。
被volatile修饰的变量V有三个规则,这三个规则要求:
- 要求在工作内存中,每次使用V前都必须先从主内存刷新最新的值,用于保证能看见其他线程对变量V所做的修改后的值;
- 要求在工作内存中,每次修改V后都必须立刻同步回主内存中,用于保证其他线程可以看到自己对变量V所做的修改;
- 这条规则要求volatile修饰的变量不会被指令排序优化,保证代码的执行顺序与程序的顺序相同。
其实我一直疑惑的是可见性,普通变量为什么不具备可见性?
https://www.cnblogs.com/tv151579/p/9395452.html
// 线程1
new Thread(new Runnable() {
public void run() {
int i = 0;
while (Test01.is) {
i++;
// String a = new String("a");
}
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程2
new Thread(new Runnable() {
public void run() {
// 设置is为false,使上面的线程结束while循环
Test01.is = false;
}
}).start();
这段代码运行时,程序不会立刻停止。因为线程1一直在计算,即CPU一直被占据着,此时CPU不会遵循jvm优化基准。而我们要向CPU遵循jvm优化基准,就要是CPU空闲下来,而此时将操作放在内存分配上时,CPU就会空闲下来,遵循jvm优化基准。
因此上面程序只要放开String a = new String(“a”);就可以结束程序。
综合上面的理解,我们应该在什么时候使用volatile?按语义上来说,应该是需要确保可见性和指令不重排时使用。但实际上开发中,CPU一般不会一直占据线程,大部分都会有内存分配等等操作,所以除非是以上这种特殊情况,大部分时候我们使用volatile应该是为了防止指令重排。当我们遇上某些变量的运用绝对不能被重排时,就可以考虑使用volatile,而不是synchronized。