java编程,如何彻底理解volatile关键字?( 五 )


3. volatile 是 Java 虚拟机提供的轻量级的同步机制
保证可见性
不保证原子性
禁止指令重排(保证有序性)
3.1 空说无凭 。代码验证
3.1.1 可见性验证
class MyData {
int number = 0;
public void add() {
this.number = number + 1;
}
}
// 启动两个线程 。一个work线程 。一个main线程 。work线程修改number值后 。查看main线程的number
private static void testVolatile() {
MyData myData = https://www.wangchuang8.com/new MyData();
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+\"\t come in\");
try {
TimeUnit.SECONDS.sleep(2);
myData.add();
【java编程,如何彻底理解volatile关键字?】System.out.println(Thread.currentThread().getName()+\"\t update number value :\"+myData.number);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, \"workThread\").start();
//第2个线程 。main线程
while (myData.number == 0){
//main线程还在找0
}
System.out.println(Thread.currentThread().getName()+\"\t mission is over\");
System.out.println(Thread.currentThread().getName()+\"\t mission is over 。main get number is:\"+myData.number);
}
}
运行方法 。输出如下 。会发现在 main 线程死循环 。说明 main 线程的值一直是 0
workThread execute
workThread update number value :1
修改 , 。在 number 前加关键字 volatile,重新运行 。main 线程获取结果为 1
workThread execute
workThread update number value :1
main execute over 。main get number is:1
3.1.2 不保证原子性验证
class MyData {
volatile int number = 0;
public void add() {
this.number = number + 1;
}
}
private static void testAtomic() throws InterruptedException {
MyData myData = https://www.wangchuang8.com/new MyData();
for (int i = 0; i < 10; i++) {
new Thread(() ->{
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
}
},\"addPlusThread:\"+ i).start();
}
//等待上边20个线程结束后(预计5秒肯定结束了) 。在main线程中获取最后的number
TimeUnit.SECONDS.sleep(5);
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(\"final value:\"+myData.number);
}
运行发现最后的输出值 。并不一定是期望的值 10000 。往往是比 10000 小的数值 。
final value:9856
为什么会这样呢 。因为在转化为字节码指令的时候是4条指令
获取原始值
将值入栈
进行加 1 操作
把后的操作写回主内存
这样在运行时候就会存在多线程竞争问题 。可能会出现了丢失写值的情况 。

java编程,如何彻底理解volatile关键字?

文章插图
如何解决原子性问题呢?
加或者直接使用原子类 。
3.1.3 禁止指令重排验证
计算机在执行程序时 。为了提高性能 。编译器和处理器常常会对指令做重排 。一般分为以下 3 种
java编程,如何彻底理解volatile关键字?

文章插图
处理器在进行重排序时必须要考虑指令之间的数据依赖性 。我们叫做语义
单线程环境里确保程序最终执行结果和代码顺序执行的结果一致;但是多线程环境中线程交替执行 。由于编译器优化重排的存在 。两个线程中使用的变量能否保证一致性是无法确定的 。结果无法预测 。
我们往往用下面的代码验证 volatile 禁止指令重排 。如果多线程环境下 。`最后的输出结果不一定是我们想象到的 2 。这时就要把两个变量都设置为 volatile 。
public class ReSortSeqDemo {
int a = 0;
boolean flag = false;
public void mehtod1(){
a = 1;
flag = true;
}
public void method2(){
if(flag){
a = a +1;
System.out.println(\"reorder value: \"+a);
}
}
}
实现禁止指令重排优化 。从而避免了多线程环境下程序出现乱序执行的现象 。
还有一个我们最常见的多线程环境中版本的单例模式中 。就是使用了 volatile 禁止指令重排的特性 。
public class Singleton {
private static volatile Singleton instance;
private Singleton(){}
// DCL
public static Singleton getInstance(){
if(instance ==null){//第一次检查
synchronized (Singleton.class){
if(instance == null){//第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}
因为有指令重排序的存在 。双端检索机制也不一定是线程安全的 。

推荐阅读