多线程-出现非线程安全的底层原因

一:什么是非线程安全

  一提到多线程,有经验的程序员就会考虑线程安全问题,那在什么情况下会出现线程安全的问题呢?

  很多人可以轻而易举的总结出:当多个线程同时竞争共享变量时会出现线程安全问题。 但是对于底层为什么会出现这种情况却不清楚了。

 

二:非线程安全的源头

  出现非线程安全的源头归因于:原子性、可见性、有序性。

  2.1:导致原子性的源头:

  很多初学者会认为i++是一个原子性操作,而i++的在cpu指令中,却分为三个步骤。

   1、从内存中读数据到cpu寄存器

   2、在cpu寄存器中执行+1指令

   3、写回到内存中。

   这三个指令在任何一步都可以在线程上下文切换时被中断,所以i++遇到上下文切换时就会导致原子性问题。

 

 

  2.2导致可见性的源头

    因为在硬件系统中,cpu和内存的速度差相差过多,所以为了平衡两者的速度差,两者之间加了一个cpu缓存。

   在多核时代,每个cpu中的线程同时去读写一个共享变量时,因为操作数据是在cpu中,所以当每个cpu中的缓存数据不能及时更新到内存中此时会导致可见性问题。

 

   2.3:导致有序性的源头:

   由于编译器、cpu、内存系统在做编译优化时,会做指令重排序,所以程序可能并不会按照我们编写代码的顺序执行。一个经典的案例,就是单例模式下的双重检查。

public class Singleton{

   static Singleton instance;
   
   private Singleton(){
   }

  public static Singleton getInstance(){
       if(instance == null){
            synchornized(Singleton.class){
               if(instance==null){

                  //不能保证有序性
                  instance=new Singleton();

                  } 
              }
        } 
       return instance;
  }
}

上面的单例看着很完美,用synchornized做了同步操作,此时执synchornized代码块中的内容时,只能一个线程去执行。然而出现上下文切换时会导致线程安全问题。

  因为  instance=new Singleton(); 并不是一个原子性操作,也是分为三步。

  1、在堆内存中为对象开辟一块空间。

  2、在空间中初始化该对象。

  3、将instance引用指向该堆内存空间。

  然而当编译优化时,出现指令重排序,顺序变为1->3->2,在第3步此时instance指向不为空,出现上下文切换,此时另外一个线程在判断  if(instance==null) ,发现不为空,直接返回一个没有被初始化的对象,就会导致线程安全性问题。

三:总结

   所以此时可以总结出,在多线程中不做额外的处理是不能保证可见性、有序性、原子性,当多个线程同时竞争共享变量时会出现线程安全性问题,所以如何保证原子性、可见性、有序性是并发编程的关键之处

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值