volatile关键字的作用

volatile关键字的作用

多线程下单变量的不可见性

在多线程并发执行下,多个线程修改共享的成员变量,会出现一个线程修改了共享变量的值后,另一个线程不能直接看到该线程修改后端变量的最新值

public class invisibleDemo01 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        new Thread(myThread , "mythread01").start();

        while (true){
                 if(myThread.isFlag()){
                     System.out.println("main线程已启动");
                 }
        }
    }
}

class MyThread implements Runnable{
    private  boolean flag = false;

    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        setFlag(true);
        System.out.println("已经修改flag为:"+isFlag());
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
//执行结果:
//已经修改flag为:true

在上面的代码中,myThread线程启动了但是线程睡眠了一秒钟那么main主线程肯定已经执行到了下面的while(true)的死循环代码,但是"main线程已启动"这句话没有打印出来,那么情况只能说是Flag是等于false的。但是明明myThread线程在睡眠过后已经将共享变量的flag的false改为了true,然而flag在main线程中还是false的状态,没有打印语句。

这就是多线程下单变量的不可见性,那为什么会导致这样的情况出现?
首先我们先看一下JMM内的主内存和工作内存他们之间的关系。
共享变量即代码里面的flag在一开始就存放在了主内存中的堆中
而每一条的线程都有一个独立的工作内存

这里写图片描述

在这里插入图片描述

1.创建的flag=false从myThread线程同步到主内存中
2.然后main线程在主内存得到flag=false
3.在一秒睡眠过后flag=true,此时main已经执行死循环,因为执行速度十分快,快到flag还没读到主内存的最新值,所以在main线程中flag一直等于false,不会打印语句。

解决不可见性的办法有两种:

加锁

public class invisibleDemo01 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        new Thread(myThread , "mythread01").start();

        while (true){
                synchronized (myThread){
                 if(myThread.isFlag()){
                     System.out.println("main线程已启动");
                 }
            }
        }
    }
}
//执行结果:
//main线程已启动
//main线程已启动
//main线程已启动
//main线程已启动...


在myThread执行完后获得myThread的锁后再执行判断,这时候就会拿到flag的值为true,不断的打印字段

添加关键字volatile

class MyThread implements Runnable{
    private volatile boolean flag = false;

    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        setFlag(true);
        System.out.println("已经修改flag为:"+isFlag());
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

工作原理:
在这里插入图片描述

1.myThread线程从主内存读取到数据放入对应的工作内存
2.将flag的值改为true,但是这个时候flag的值还没有写入到主内存
3.此时main方法读取到了flag的值为false
4.当myThread线程将flag的值写回去后,失效其他线程对此变量的副本
5.再次堆flag进行操作的时候线程会从主内存读取最新的值,放入到工作内存中
总结:volatile保证不同线程堆共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另一个线程立即看到最新的值

非原子性

volatile关键字的添加不能保证线程的原子性,有实现原子性操作需要使用原子类属性或者在程序上加锁。

禁止指令重排

在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分3种类型:

1.编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

2.指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

3.内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

而禁止指令重排可以避免一些,多线程下的一些极端状态的出现(数据有偏差)

sm,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

3.内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

而禁止指令重排可以避免一些,多线程下的一些极端状态的出现(数据有偏差)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值