您的位置:首页 > 编程语言 > Java开发

java设计模式之单例模式写法,懒汉,饿汉,双检锁

2014-10-14 10:26 459 查看
关键字: singleton 单例 写法 双锁 线程安全

饿汉式单例类

public class Singleton   

{   

    private Singleton(){   

       

    }   

  

    private static Singleton instance = new Singleton();
  

  

    private static Singleton getInstance(){   

        return instance;   

    }   

}  

 饿汉式提前实例化,没有懒汉式中多线程问题,但不管我们是不是调用getInstance()都会存在一个实例在内存中

内部类式单例类

public class Singleton      

{         

        private Singleton(){      

          

    }      

     

    private class SingletonHoledr(){      

        private static Singleton instance = new Singleton();   
  

    }      

     

    private static Singleton getInstance(){      

        return SingletonHoledr.instance;      

    }      

}   

内部类式中,实现了延迟加载,只有我们调用了getInstance(),才会创建唯一的实例到内存中.并且也解决了懒汉式中多线程的问题.解决的方式是利用了Classloader的特性.

 

懒汉式单例类

public class Singleton      

{         

    private Singleton(){      

     

    }      

     

    private static Singleton instance;      

    public static Singleton getInstance(){      

        if(instance == null){      

            return instance = new Singleton();      

        }else{      

            return instance;      

        }      

    }      

}    

 

在懒汉式中,有线程A和B,当线程A运行到第8行时,跳到线程B,当B也运行到8行时,两个线程的instance都为空,这样就会生成两个实例。解决的办法是同步:

可以同步但是效率不高:

 双检锁写法: 


public class Singleton{   

  private static Singleton instance;    //声明静态的单例对象的变量  

  private Singleton(){}    //私有构造方法   

     

  public static Singleton getSingle(){    //外部通过此方法可以获取对象    

    if(instance== null){      

        synchronized (Singleton.class) {   //保证了同一时间只能只能有一个对象访问此同步块        

            if(instance== null){       

                instance= new Singleton();           

        }      

      }   

    }     

    return instance;   //返回创建好的对象   

  }   




 

    为什么要在 if 语句中使用两次判断
instance== null ,这里涉及到一个名词 Double-Check Locking ,也就是双重检查锁定,为何要使用双重检查锁定呢?

      考虑这样一种情况,就是有两个线程同时到达,即同时调用 GetInstance(),此时由于
instance== null ,所以很明显,两个线程都可以通过第一重的
instance== null ,进入第一重 if 语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重
instance== null ,而另外的一个线程则会在 lock 语句的外面等待。而当第一个线程执行完 new  Singleton()语句后,便会退出锁定区域,此时,第二个线程便可以进入 lock 语句块,此时,如果没有第二重
instance== null 的话,那么第二个线程还是可以调用 new  Singleton()语句,这样第二个线程也会创建一个 Singleton 实例,这样也还是违背了单例模式的初衷的,所以这里必须要使用双重检查锁定。
       细心的朋友一定会发现,如果我去掉第一重
instance== null ,程序还是可以在多线程下完好的运行的,考虑在没有第一重
instance== null 的情况下,当有两个线程同时到达,此时,由于 lock 机制的存在,第一个线程会进入 lock 语句块,并且可以顺利执行 new Singleton(),当第一个线程退出 lock 语句块时,
instance这个静态变量已不为 null 了,所以当第二个线程进入 lock 时,还是会被第二重
instance== null 挡在外面,而无法执行 new Singleton(),所以在没有第一重
instance== null 的情况下,也是可以实现单例模式的?那么为什么需要第一重
instance== null 呢?这里就涉及一个性能问题了,因为对于单例模式的话,new Singleton()只需要执行一次就 OK 了,而如果没有第一重
instance== null 的话,每一次有线程进入 GetInstance()时,均会执行锁定操作来实现线程同步,这是非常耗费性能的,而如果我加上第一重
instance== null 的话,那么就只有在第一次,也就是
instance ==null 成立时的情况下执行一次锁定以实现线程同步,而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了,这样就可以解决由线程同步带来的性能问题了。

思路很简单,就是我们只需要同步(synchronize)初始化instance的那部分代码从而使代码既正确又很有效率。

这就是所谓的“双检锁”机制(顾名思义)。

很可惜,这样的写法在很多平台和优化编译器上是错误的。

原因在于:instance = new Singleton()这行代码在不同编译器上的行为是无法预知的。一个优化编译器可以合法地如下实现instance = new Singleton():

1. instance  = 给新的实体分配内存

2. 调用Singleton的构造函数来初始化instance的成员变量

现在想象一下有线程A和B在调用getInstance,线程A先进入,在执行到步骤1的时候被踢出了cpu。然后线程B进入,B看到的是instance  已经不是null了(内存已经分配),于是它开始放心地使用instance,但这个是错误的,因为在这一时刻,instance的成员变量还都是缺省值,A还没有来得及执行步骤2来完成instance的初始化。

当然编译器也可以这样实现:

1. temp = 分配内存

2. 调用temp的构造函数

3. instance = temp

如果编译器的行为是这样的话我们似乎就没有问题了,但事实却不是那么简单,因为我们无法知道某个编译器具体是怎么做的,因为在Java的memory model里对这个问题没有定义。


双检锁对于基础类型(比如int)适用。很显然吧,因为基础类型没有调用构造函数这一步
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息