Java Volatile关键字

Java Volatile关键字

volatile是java虚拟机提供的一种轻量级同步机制
volatile有三大特性

  • 保证可见性
  • 不保证原子性
  • 禁止指令重排

保证可见性

即线程在自己工作内存产生的变化在其他线程中是可见的。当线程改变值的时候,cpu会通知其他线程,该值失效,请重新从主存中读取。
简单的理解就是大家都在主存中读写


class MyData1 {
    int num = 0;

    public void add() {
        this.num = 1;
    }
}

class MyData2 {
    //使用volatile修饰
    volatile int num = 0;

    public void add() {
        this.num = 1;
    }
}

/**
 * volatile可见性测试demo
 */
public class VolatileTest {
    public static void main(String[] args) {
        //新建两个内部类
        MyData1 myData = new MyData1();
        MyData2 myData2 = new MyData2();
        //这里已经知道结果了,所以直接test2先执行
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "开始执行");
            try {
                //这里睡一秒,等主线程拿到mun
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData2.add();
        }, "test2").start();
        //循环判断值变化
        while (myData2.num == 0) {
        }
        System.out.println("mun2不等于0");
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "开始执行");
            try {
                //这里睡一秒,等主线程拿到mun
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.add();
        }, "test1").start();
        while (myData.num == 0) {
        }
        System.out.println("mun不等于0");
    }
}


test2开始执行
mun2不等于0
test1开始执行

不保证原子性

原子性:不可分割,完整性,即某个业务要么同时成功,要么同时失败
如下demo,本应该输出20000,实际输出19673,并不能保证原子性

class MyData1 {
    volatile int num = 0;

    public void add() {
        num++;
    }
}
/**
 * volatile原子性测试demo
 */
public class VolatileTest {
    public static void main(String[] args) {
        MyData1 mydata = new MyData1();
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    mydata.add();
                }
            },"aa").start();

        }
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(mydata.num);
    }
}

19673

禁止指令重排

计算机在执行程序的时候,为了提高性能,编译器和处理器会对指令做重排

oa=>operation: 源代码
ob=>operation: 编译器优化重排
oc=>operation: 指令并行的重排
od=>operation: 内存系统的重排
oe=>operation: 最终执行的指令
oa(right)->ob(right)->oc(right)->od(right)->oe

即:单线程环境里面可以确保程序最终执行结果一致。
处理器在进行重排序时必须考虑指令之间的数据依赖性(即现有鸡再有蛋)。
多线程环境中线程交替执行,由于编译器优化排序的存在,两个线程中的变更能否保证一致性是无法确定的,结果无法预测。

int a = 1;
int b = 2;
a++;
b=a+1;
//这些指令可以按以下顺序重排,而不改变程序的语义
int a = 1;
a++;
int b = 2;
b=a+1;
//
int b = 2;
int a = 1;
a++;
b=a+1;

最经典的应用就是双端检索懒加载的单例模式
双端检索机制不能100%保证指令执行的顺序,可能百分之99是正确的,但是发生指令重排,可能就会导致出错

posted @ 2020-08-12 23:41  escapeorface  阅读(88)  评论(0)    收藏  举报