线程与并发 - volatile

多线程与高并发
  • volatile 保证线程间可见性,让一个变量再多个线程间是可见的
  • volatile 禁止指令重排序
  • volatile 不能保证原子性
  • java内存:堆内存是所有线程共享的内存,并且每个线程都有属于自己的工作内存,当线程需要访问某一个值时,会复制一份值放在自己得工作内存中,当线程执行完毕将变化后的值再写回堆中。
  • java语言:是需要经过编译器编译的,往往我们看似是一行的代码,编译之后会变成多个指令操作。
  • cpu执行指令:为了提高效率现在cpu不再是一步一步执行的了,可能你的第一个指令执行一半的时候,第二个指令就已经开始执行了,在这种执行设计上要求编译器把你的代码编译完之后的指令可以进行一个指令重排。
线程间可见性

启动t1线程,t1会将on=true读取到自己线程的工作内存中,并不是每次都去读取主存中on的值,当main将t的on更改为false时,t1并不知道,所以程序会一直执行;当利用volatile修饰的时候,on的值发生改变时就会通知其它线程,要求其他线程从新读取主存中的变量值,这时t1就可以读取到false,跳出循环。

public class Thread_012 {
    /*volatile*/ boolean on = true;
    private void m(){
        while(on){
        }
    }
    public static void main(String[] args) {
        Thread_012 t = new Thread_012();
        new Thread(t::m,"t1").start();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.on = false;
    }
}
执行结果:
不加volatile 任务不会停止。
加volatile 任务可以正常停止。
禁止指令重排序
  • idea下载插件 jclasslib Bytecode viewer 可以观测编译后的代码

Object o = new Object(); 编译后:通过工具我们可以看到一个new 操作实际编译后被解析成了5步指令。这样再cpu执行的时候可能会造成,astore_1再我的初始化还没有完成就将地址赋给了引用,这时候引用拿到的值是默认值而不是初始化之后的值。
bytecode

面试题:单例中的双重检查写法是否需要对单例加volatile修饰?
答:需要。我们来看下面这段代码:

其实这段代码你不加volatile,怎么实验也是不会出错的,但是它其实时存在问题的,这个问题就出在指令重排序上。

public class Thread_013 {
    static volatile Thread_013 INSTANCE;
    private Thread_013(){}
    public Thread_013 getINSTANCE(){
        if(INSTANCE == null){
            synchronized (Thread_013.class){
                if(INSTANCE == null){
                    INSTANCE = new Thread_013();
                }
            }
        }
        return INSTANCE;
    }
}

当t1线程进来走到new Thread_013()时,我的指令再初始化之前将地址赋给了引用这样t2进来返现INSTANCE不是null的了就直接返回了默认值,并没有拿到我们初始化的 new Object();这就是我们需要禁止指令重排的地方,加上volatile之后,就会强制要求cpu再执行这几行指令时,必须等待初始化完成之后才能将地址赋给引用。

volatile不能保证原子性,不能替代synchronized
public class Thread_014 {
    volatile int count = 0;

    /*synchronized*/ void add(){
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }

    public static void main(String[] args){
        Thread_014 t = new Thread_014();
        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add(new Thread(t::add));
        }

        list.forEach(t1 -> t1.start());
        list.forEach(thread ->{
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        System.out.println("count:"+t.count);
    }
}
执行结果:count < 100000

看到这里,小朋友是不是有很多问号?volatile保证线程间的可见性,t1线程修改完count值之后,通知其它线程让他们重新去主存读取,为什么最后的值确不是10W这个预期的结果?
个人理解:t1~t4同时读取到了0放在自己的工作内存中,当t1修改完去通知t2,t3,t4…时,可能t4再收到t1通知时也正在将改变的值写回主存,这个写操作是原子性的必须执行完,所以我即使知道你这个值已经改了,但是我也没有办法再去修改了,可见性来的晚了一些。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值