概念
- Volatile 是 JVM 提供的轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
使用volatile解决不可见性场景
package study_volatitle;
import java.util.concurrent.TimeUnit;
/**
* 演示不可见性
*/
public class JMMDemo1 {
private static Boolean flag = true;
public static void main(String[] args) {
new Thread(()->{
while(flag){
}
},"其他线程").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程修改了flag,但是另外一个线程工作空间的flag仍然是true.");
flag = false;
}
}
解决不可见性的方案
- 增加Lock锁或synchronized关键字
- 增加volatile关键字:
private static volatile Boolean flag = true;
使用volatile不保证原子性场景
package study_volatitle;
public class VolatileDemo2 {
private static int num = 0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
// 确保上面的线程执行完,只剩下 main 和 gc 线程
while(Thread.activeCount() > 2){
Thread.yield();//当前主线程 暂时停止执行
}
System.out.println("正常结果为100,输出结果为===》" + num); // 正常结果为10000,输出结果为===》9182
}
public static void add(){
num++;
}
}
============ 加上volatile =========
package study_volatitle;
public class VolatileDemo2 {
private static volatile int num = 0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
// 确保上面的线程执行完,只剩下 main 和 gc 线程
while(Thread.activeCount() > 2){
Thread.yield();//当前主线程 暂时停止执行
}
System.out.println("正常结果为100,输出结果为===》" + num); // 正常结果为10000,输出结果为===》9991
}
public static void add(){
num++;
}
}
分析
-
多个线程操作数据时,会出现不准确结果
-
发现加上volatile人仍然不能保证原子性,因为num++的底层并不是一个原子性操作,对应三个字节指令
-
加上
synchronized
关键字 、Lock锁可以保证原子性
解决使用volatile保证原子性
解决方案
-
使用java.util.concurrent.atomic 下的一个小型工具包,配合
volatile
支持单个变量上的无锁线程安全编程。 -
// 创建 可原子更新的int 值对象
private static volatile AtomicInteger num = new AtomicInteger(0);// 该对象有方法+1,原理是调用Unsafe类的方法,底层使用的CAS
num.getAndIncrement();
package study_volatitle; import java.util.concurrent.atomic.AtomicInteger; public class AtomicDemo3 { // 创建 可原子更新的int 值对象 private static volatile AtomicInteger num = new AtomicInteger(0); public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ for (int i1 = 0; i1 < 1000; i1++) { // 该对象有方法+1,底层使用的CAS num.getAndIncrement(); } }).start(); } // 确保上面的线程执行完,只剩下 main 和 gc 线程 while(Thread.activeCount() > 2){ Thread.yield();//当前主线程 暂时停止执行 } System.out.println("正常结果为10000,输出结果为===》" + num); // 正常结果为10000,输出结果为===》10000 } }
禁止指令重排
什么是指令重排
--程序代码的执行并不一定会按照 源代码的顺序执行。
---大致流程源代码—>编译器优化的重排—>指令并行的重排—>内存系统也会重排—>执行
---处理器 会根据数据之间的依赖性 对指令进行重排
---加入volatile会避免指令重排,内存中有屏障,作用
---保证特定的执行顺序
---可以保证某些变量的内存可见性