并发编程之三个主要问题(可见性,原子性,有序性)

27 篇文章 0 订阅
4 篇文章 0 订阅

可见性、原子性和有序性问题

CPU、内存和IO设备随着时代发也在不断更新迭代,不过更新迭代的同时,他们之间的矛盾也一直存在,三者存在巨大的速度差异(CPU>内存>IO)。
操作系统的整体性能就取决于它的·短板:IO读写,为此为了平衡他们之间的速度差异,操作系统和计算机体系也提供了许多对策来平衡他们之间的速度:
1、增加CPU缓存,平衡CPU和内存的速度差异。
2、操作系统增加了进程和线程,均衡CPU和IO设备的速度差异
3、编译程序优化指令执行次序,是缓存得到更加合理的使用
机遇和风险并存,有相应的对策,自然会引入其他问题。
问题一:引入CPU缓存带来的可见性问题
何为可见性:可见性就是一个线程对共享变量的修改,另一个线程能够立即看到,即称为可见性
单核时代所有线程都在一颗CPU上运行,所以用的都是同一块缓存,所以一个线程对缓存的读写对于另一个线程来说一定是可见的。多核时代每颗CPU都有自己的缓存,当多个线程在不同cpu上执行时,操作的是不同的CPU缓存,因此就不具备可见性了
问题二:线程切换带来的原子性问题
一个或者多个操作在CPU执行过程中不被中断的特性称为原子性,CPU能保证原子操作是针对于CPU本身指令,而非高级语言指令层面,所以很多时候我们需要在高级语言层面保证操作的原子性
问题三:编译优化带来的有序性问题
下面一个例子双重检验创建单例单例对象public class

Singleton{
    static Singleton instance;
    public static Singleton getInstance(){
        if(instance == null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

如果线程A\B同时访问上面的方法,他们会发现instance==null,于是同时对Singleton进行加锁,此时JVM保证只有一个线程能加锁成功,另一个线程处于等待状态。看起来一切都很美好,可实际不然,问题出在new操作上面:1、分配一块内存M;2、在内存M上初始化Singleton对象;3、然后将M的地址赋值给instance变量编译优化后会出现以下情况:1、分配一块内存M;2、将M的地址赋值给instance变量;3、最后再在M地址上初始化Singleton对象。这样的编译优化后在指令2,instance就不为null,这个时候如果线程切换到B线程,B线程刚好调用getInstance方法,因为instance不为null,所以直接就返回instance,但是这instance是还没初始化的,就可能出现异常。解决方式是对instance进行volatile语义声明(保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)禁止进行指令重排序。(实现有序性)volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。)

补充总结:cpu的缓存不位于内存中,至于系统啥时候把数据写进缓存中,这个没有固定时间。使用volatile这个关键字可以时线程A执行完后的值强制从缓存刷入到内存中,而线程B在执行时候,会强制读取内存中的数字来更新缓存。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值