欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-05 22:49:08
...

一:什么是非线程安全

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

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

 

二:非线程安全的源头

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

  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) ,发现不为空,直接返回一个没有被初始化的对象,就会导致线程安全性问题。

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

三:总结

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