单例模式的双重检测

单例模式是设计模式中比较常见简单的一种,典型双重检测写法如下:

public class SingletonClass { 

  private volatile static SingletonClass instance = null; 

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

接下来对该写法进行分析,为何这样写?

一、为何要同步:

多线程情况下,若是A线程调用getInstance,发现instance为null,那么它会开始创建实例,如果此时CPU发生时间片切换,线程B开始执行,调用getInstance,发现instance也null(因为A并没有创建对象),然后B创建对象,然后切换到A,A因为已经检测过了,不会再检测了,A也会去创建对象,两个对象,单例失败。因此要同步。

二、同步为何不用 public synchronized static SingletonClass getInstance(),也就是说为何不同步这个方法,而要同步下面的语句:

因为synchronized修饰的同步块可是要比一般的代码段慢上几倍,如果经常调用getInstance,那么性能问题就得考虑了。

三、最外层为何要有if (instance == null)判断:

因为我们在分析二中,发现依旧存在着性能问题,也就是说,只要getInstance方法被调用,那么就会执行同步这个操作,于是我们加个判断,当instance没有被实例化的时候,也就是需要去实例化的时候才去同步。

四、instance为何要有volatile 修饰:

这个问题就涉及到了编译原理,所谓编译,就是把源代码“翻译”成目标代码——大多数是指机器代码——的过程。针对Java,它的目标代码不是本地机器代码,而是虚拟机代码。编译原理里面有一个很重要的内容是编译器优化。所谓编译器优化是指,在不改变原来语义的情况下,通过调整语句顺序,来让程序运行的更快。这个过程成为reorder。

JVM实现可以自由的进行编译器优化。而我们创建变量的步骤:

1、申请一块内存,调用构造方法进行初始化。

2、分配一个指针指向这块内存。

而这两个操作,JVM并没有规定谁在前谁在后,那么就存在这种情况:线程A开始创建SingletonClass的实例,此时线程B调用了getInstance()方法,首先判断instance是否为null。按照我们上面所说的内存模型,A已经把instance指向了那块内存,只是还没有调用构造方法,因此B检测到instance不为null,于是直接把instance返回了——问题出现了,尽管instance不为null,但它并没有构造完成,就像一套房子已经给了你钥匙,但你并不能住进去,因为里面还没有收拾。此时,如果B在A将instance构造完成之前就是用了这个实例,程序就会出现错误了。

在JDK 5之后,Java使用了新的内存模型。volatile关键字有了明确的语义——在JDK1.5之前,volatile是个关键字,但是并没有明确的规定其用途——被volatile修饰的写变量不能和之前的读写代码调整,读变量不能和之后的读写代码调整!因此,只要我们简单的把instance加上volatile关键字就可以了。

转载请标明出处:https://www.cnblogs.com/tangZH/p/10031337.html 

参考链接:http://blog.51cto.com/devbean/203501

原文地址:https://www.cnblogs.com/tangZH/p/10031337.html

时间: 2024-11-09 16:27:15

单例模式的双重检测的相关文章

单例模式和双重检测的小结

设计模式的经典模式之一--单例模式 这个也是面试经常被问起的面试,一般都要求手写 [饿汉模式]非延时加载 --以空间换时间 [懒汉模式]延时加载--以时间换空间 看似简单,但是在懒汉模式还隐藏了一个双重检测,其中还是涉及到JVM内存的"无序写入"问题(后面使用的 volatile ) 1.饿汉模式 比较简单,非延时加载,一上来就把对象给new出来了 <span style="font-family:Microsoft YaHei;font-size:14px;"

单例模式之双重检测锁

先来看看双重检测锁的实现以及一些简要的说明(本文主要说明双重检测锁带来的线程安全问题): /** * 单例模式之双检锁 * @author ring2 * 懒汉式升级版 */ public class Singleton3 { private static volatile Singleton3 instance; private Singleton3() {} public static Singleton3 getInstance() { //首先判断是否为空 if(instance==nu

Java并发笔记——单例与双重检测

单例模式可以使得一个类只有一个对象实例,能够减少频繁创建对象的时间和空间开销.单线程模式下一个典型的单例模式代码如下: ① 1 class Singleton{ 2 private static Singleton singleton; 3 private Singleton(){} 4 5 public static Singleton getInstance(){ 6 if(singleton == null){ 7 singleton = new Singleton(); //1 8 }

双重检测单例类

单例类有很多种,有饿汉式,懒汉式.其中懒汉式由于其两次判断被称为双重检测单例类. 看一段代码. 1 public class StoreKeeper { 2 /** 属性列表值. */ 3 private HashMap<String, Store> storepool = null; 4 private HashMap<String, Long> lifepool = null; 5 private static StoreKeeper instance; 6 private S

单例模式,双重校验的懒汉式

/** * 单例模式,双重校验的懒汉式 */ public class bTestSingleton { public static void main(String[] args) { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println(s1==s2); } } class Singleton{ private Singleton(){ } priv

单例模式、双检测锁定DCL、volatile详解

单例模式最要关心的则是对象创建的次数以及何时被创建. Singleton模式可以是很简单的,它的全部只需要一个类就可以完成(看看这章可怜的UML图).但是如果在"对象创建的次数以及何时被创建"这两点上较真起来,Singleton模式可以相当的复杂,比头五种模式加起来还复杂,譬如涉及到DCL双锁检测(double checked locking)的讨论.涉及到多个类加载器(ClassLoader)协同时.涉及到跨JVM(集群.远程EJB等)时.涉及到单例对象被销毁后重建等.对于复杂的情况

单例模式、双检测锁定DCL、volatile(转)

单例模式最要关心的则是对象创建的次数以及何时被创建. Singleton模式可以是很简单的,它的全部只需要一个类就可以完成(看看这章可怜的UML图).但是如果在“对象创建的次数以及何时被创 建”这两点上较真起来,Singleton模式可以相当的复杂,比头五种模式加起来还复杂,譬如涉及到DCL双锁检测(double checked locking)的讨论.涉及到多个类加载器(ClassLoader)协同时.涉及到跨JVM(集群.远程EJB等)时.涉及到单例对象被销毁后重建 等.对于复杂的情况,本章

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

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

Java单例模式中双重检查锁的问题

单例创建模式是一个通用的编程习语.和多线程一起使用时,必需使用某种类型的同步.在努力创建更有效的代码时,Java 程序员们创建了双重检查锁定习语,将其和单例创建模式一起使用,从而限制同步代码量.然而,由于一些不太常见的 Java 内存模型细节的原因,并不能保证这个双重检查锁定习语有效. 它偶尔会失败,而不是总失败.此外,它失败的原因并不明显,还包含 Java 内存模型的一些隐秘细节.这些事实将导致代码失败,原因是双重检查锁定难于跟踪.在本文余下的部分里,我们将详细介绍双重检查锁定习语,从而理解它