双重检查锁实现单例模式的线程安全问题

一、结论

双重校验锁的单例模式代码如下:

public class Singleton {
  private static Singleton singleton;

  private Singleton() {}

  public static Singleton getSingleton() {
    if (singleton == null) { // 1
      synchronized (Singleton.class) { // 2
        if (singleton == null) { // 3
          singleton = new Singleton(); // 4
        }
      }
    }
    return singleton;
  }
}

假设有两个线程AB同时访问上面这段代码,它并不能保证线程安全。

二、问题说明

  1、指令重排序的简单说明

  重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

  (1)编译器指令重排序

    编译器在不改变程序 执行结果的前提下,可以对程序的执行顺序进行优化重新排序

     (2) 处理器指令重排序

    参考:https://blog.csdn.net/javazejian/article/details/72772461 (处理器指令重排)

  2、 对象创建过程

    分为三步,如下图:

我们认为程序应该是按照1、2、3的步骤走下去,但实际上可能不是这样的,这里编译器和处理器可能会对2、3步的执行顺序进行重排序,即先将对象的引用指向内存空间,实际上A线程返回的是没有初始化的对象,然后B线程访问上面这段代码,判断if (singleton == null) { // 1 就为false,它会认为Singleton类已经实例化,问题就出在这里。

  重排序后A 、B线程执行时序图如下:

三、解决方案

  1、不允许对象创建过程中2、3步发生指令重排序 (基于volatile的解决方案)

   即将Singleton声明时加上volatile,volatile关键字可以保证内存可见性和禁止指令重排序,关于volatile参见https://blog.csdn.net/javazejian/article/details/72772461 (volatile内存语义)

    修改后的代码:

    public class Singleton {
      private volatile static Singleton singleton;

      private Singleton() {}

      public static Singleton getSingleton() {
        if (singleton == null) { // 1
          synchronized (Singleton.class) { // 2
            if (singleton == null) { // 3
              singleton = new Singleton(); // 4
            }
          }
        }
        return singleton;
      }
    }

    Singleton属性被加上volatile后,4中对象创建过程的2、3两步在多线程环境下就被禁止重排序,这样就能保证线程安全。

  2、允许对象创建过程中2、3重排序,但不允许其他线程看到这个重排序 (基于类初始化的解决方案)

    JVM在类的初始化阶段,会执行类的初始化。在执行类的初始化期间,JVM会获取一个锁,这个锁可以同步多个线程对同一个类的初始化。基于这个特性修改代码如下:

    public class Singleton {
      private static class SingletonHolder{
        public static Singleton singleton = new Singleton();
      }
      public static Singleton getSingleton(){
        return SingletonHolder.singleton;
      }
    }

        多线程访问上面这段程序的时序图如下:

   

参考资料:

  1、https://blog.csdn.net/javazejian/article/details/72772461

  2、《Java并发编程的艺术》第三章 Java内存模型

    

说明:菜鸟一枚,第一次发技术博客,如有错误或者写的不好的地方欢迎大家指正。

原文地址:https://www.cnblogs.com/-Marksman/p/9219274.html

时间: 2024-10-06 21:18:14

双重检查锁实现单例模式的线程安全问题的相关文章

线程安全的单例模式及双重检查锁—个人理解

在web应用中服务器面临的是大量的访问请求,免不了多线程程序,但是有时候,我们希望在多线程应用中的某一个类只能新建一个对象的时候,就会遇到问题. 首先考虑单线程,如果要求只能新建一个对象,那么构造函数我们要设为private.简单的想法: class singleton{ private singleton(){ //..... } private static singleton instance; public static singleton getinstance(){ if(insta

单例模式中用volatile和synchronized来满足双重检查锁机制

背景:我们在实现单例模式的时候往往会忽略掉多线程的情况,就是写的代码在单线程的情况下是没问题的,但是一碰到多个线程的时候,由于代码没写好,就会引发很多问题,而且这些问题都是很隐蔽和很难排查的. 例子1:没有volatile修饰的uniqueInstance public class Singleton { private static Singleton uniqueInstance; private Singleton(){ } public static Singleton getInsta

双重校验锁实现单例模式(对象单例,线程安全)

双重校验锁实现单例模式: public class Singleton { //采用volatile修饰 private volatile static Singleton singleton; //构造方法私有化 private Singleton(){} //双重校验锁 public static Singleton getInstance(){ //先判断对象是否已经实例过,没有实例化过才进入加锁代码 if(singleton == null){ //类对象加锁 synchronized(

单例模式中 的 双重检查锁 概念与用法

public class Singleton { //私有的 静态的 本类属性 private volatile static Singleton _instance; //私有化构造器 private Singleton() {} /* * 1st version: creates multiple instance if two thread access * this method simultaneouslyX */ public static Singleton getInstance

[杂谈]C++的双重检查锁并不安全

原文地址 http://www.cnblogs.com/hebaichuanyeah/p/6298513.html 一个典型的单例模式构建对象的双重检查锁如下: static Singleton * getSingleObject() { if(singleObject==NULL) { lock(); if(singleObject==NULL) { singleObject = new Singleton(); } unlock(); } return singleObject; } 该代码

C++的双重检查锁并不安全(转)

一个典型的单例模式构建对象的双重检查锁如下: 1 static Singleton * getSingleObject() 2 { 3 if(singleObject==NULL) 4 { 5 lock(); 6 if(singleObject==NULL) 7 { 8 singleObject = new Singleton(); 9 } 10 unlock(); 11 } 12 return singleObject; 13 } 该代码的逻辑是:getSingleObject()函数获得对象

关于并发场景下,通过双重检查锁实现延迟初始化的优化问题隐患的记录

首先,这个问题是从<阿里巴巴Java开发手册>的1.6.12(P31)上面看到的,里面有这样一句话,并列出一种反例代码(以下为仿写,并非与书上一致): 在并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优化问题隐患,推荐解决方案中较为简单的一种(适用于JDK5及以上的版本),即目标属性声明为volatile型. 1 public class Singleton { 2 private static Singleton instance=null; 3

单例陷阱——双重检查锁中的指令重排问题

之前我曾经写过一篇文章<单例模式有8种写法,你知道么?>,其中提到了一种实现单例的方法-双重检查锁,最近在读并发方面的书籍,发现双重检查锁使用不当也并非绝对安全,在这里分享一下. 单例回顾 首先我们回顾一下最简单的单例模式是怎样的? /** *单例模式一:懒汉式(线程安全) */ public class Singleton1 { private static Singleton1 singleton1; private Singleton1() { } public static Singl

从单例的双重检查锁想到的

常说的单例有懒汉跟饿汉两种写法.饿汉由于类加载的时候就创建了对象,因此不存在并发拿到不同对象的问题,但会由于开始就加载了对象,可能会造成一些启动缓慢等性能问题:而懒汉虽然避免了这个问题,但普通的写法会在高并发环境下创建多个对象,单纯加synchronize又会明显降低并发效率,较好的两种写法是静态内部类跟双重检查锁两种. 双重检查锁这个,大家都很熟悉了,上代码: public class SingleTest { private static SingleTest singleTest; //获