zoukankan      html  css  js  c++  java
  • 【设计模式篇】单例模式

    01 单例模式

      单例模式,只有一个实例存在于整个JVM中,保证只有一个实例,并可以被外界访问。它是一种常用的设计模式之一。实现单例模式

    的方法有很多种,然而需要考虑包括线程安全在内的一些因素。以下列举了几种典型的实现方法。

    02 实现及问题

     方法一:懒汉式实现

      【懒汉式】私有化构造函数,创建静态方法,提供单例引用,延迟加载。 重大缺陷:线程不安全,线程A希望能够使用Singleton

    实例,于是第一次调用静态方法getInstance(),发现此时singleton==null,准备创建实例,突然CPU发生时间片切换,线程B也希望

    调用该实例,此时A并未创建,因此singleton==null,B创建Singleton实例,执行完成后,线程A继续执行,因为已经判断过singleto

    n==null,所以也创建了一个实例对象。 最终导致单例失败。

     1 /**
     2  * 懒汉式 写法1
     3  * @author Administrator
     4  *
     5  */
     6 public class Singleton {
     7     private Singleton(){
     8         
     9     }
    10     private static Singleton singleton=null;
    11     //静态工厂方法
    12     public static Singleton getInstance(){
    13         if(singleton==null){
    14             singleton=new Singleton();
    15         }
    16         return singleton;
    17     }
    18 }

     方法二:饿汉式实现

       【饿汉式】懒汉式存在线程安全问题,无法在多线程下实现单例。饿汉式的方法就是可以在类加载时创建一个静态实例。其缺点在

    于类的初始化开销大。然而却是天生安全的。

     1 /**
     2  * 饿汉式:类初始化时,实例化。
     3  * @author Administrator
     4  *
     5  */
     6 public class Singleton3 {
     7     private Singleton3(){
     8         
     9     }
    10     private static final Singleton3 singleton=new Singleton3();
    11     public static Singleton3 getInstance(){
    12         return singleton;
    13     }
    14 }

    方法三:同步懒汉式1

      【synchronized】在静态方法getInstance()前添加synchronized同步关键字实现方法同步, 重大缺点:执行效率低。每一次调

    用getInstance()都需要加锁,而加锁是非常耗时的,应尽量避免。

     1 /**
     2  * 同步懒汉式1,添加synchronized
     3  * @author Administrator
     4  *
     5  */
     6 public class Singleton1 {
     7     private Singleton1(){
     8         
     9     }
    10     private static Singleton1 singleton=null;
    11     //静态工厂方法
    12     public static synchronized Singleton1 getInstance(){
    13         if(singleton==null){
    14             singleton=new Singleton1();
    15         }
    16         return singleton;
    17     }
    18 }

    方法四:双重检测懒汉式

       双重检测:即在同步块前后分别判断实例是否不为空。可以减少同步数。只有当instance==null的时候,才需要进入同步块,进行

    加锁操作。当已经创建后,则无需进入同步块,即不需要加锁操作,只有第一次需要创建实例对象时,多个线程才需要进入同步块,节

    时间。

     1 /**
     2  * 双重检测懒汉式:双重检测,提高效率
     3  * @author Administrator
     4  *
     5  */
     6 public class Singleton2 {
     7     private Singleton2(){
     8         
     9     }
    10     private static volatile  Singleton2 singleton=null;
    11     //静态工厂方法
    12     public static Singleton2 getInstance(){
    13         if(singleton==null){    //若该线程检测到不为空,则不用创建,否则,进入同步块
    14             synchronized(Singleton2.class){
    15                 if(singleton==null){    //多个线程开始同步,若一个线程创建成功,则其它后进入的线程都不用创建
    16                     singleton=new Singleton2();
    17                 }
    18             }
    19             
    20         }
    21         return singleton;
    22     }
    23 }

      在双重检测中,使用了 volatile 关键字,能够保证访问到的变量是最后修改的。为什么要加volatile关键字呢?因为在Java中双重

    检测很可能导致失败的结果,得到一个存在但未初始化完成的实例。《Java与模式》的总结是:在Java编译器中,Singleton类的初

    化与instance变量赋值的顺序不可预料。如果一个线程在没有同步化的条件下读取instance引用,并调用这个对象的方法的

    话,可能会发现对象初始化过程尚未完成,从而造成崩溃

      其原因在于 Java虚拟机中实现的 无序写入 问题。所谓无序写入,JVM编译器能用不同的顺序从程序的解释中生成指令,并且存储

    变量在寄存器中,而并非内存,处理器可以并行或者颠倒次序的执行指令。完成的结果与严格连续执行的结果一致。例如在双重检测中

    singleton=new Singleton() 语句可能包含下列伪代码表示的指令。

      ----------------------------------------------------------

      memory=allocate(); //1、分配内存

      memory=Singleton();  //2、初始化实例

      singleton=memory;  //3、将实例赋值给引用变量。

      ----------------------------------------------------------

      由于JVM中存在无序写入 问题,那么很可能先执行语句3,再执行语句2。即如下所以:

      ----------------------------------------------------------

      memory=allocate();  //1、分配内存

      singleton=memory();  //2、将未初始化的实例复制给引用变量

      memory=Singleton();  //3、实例初始化

      ----------------------------------------------------------

      如果在执行语句2后,线程切换成另外一个线程,此时线程可知 instance!=null,然而此时该实例却是未初始化的详细解释

    参考文献[1]。

      那么是否加了volatile关键字 双重检测就达到最优了呢?不是,因为volatile与synchronized有相当的含义,访问变量时

    同synchronized。且由于两次检测,相比synchronized block 效率更低一些。[1]

     方法五: 静态内部类

       使用静态内部类存放静态实例对象。在加载Singleton类时,并不加载内部类,在调用getInstance()时调用内部类,静态实例时,

    加载静态内部类和内部静态实例对象。从而达到延时加载的目的。

     1 /**
     2  * 懒汉式:通过静态内部类实现方式,既实现了线程安全,又提高了效率
     3  * @author Administrator
     4  *
     5  */
     6 public class Singleton4 {
     7     private Singleton4(){
     8         
     9     }
    10     public static Singleton4 getInstance(){
    11         return Instance.singleton;    //当需要返回实例的时候,初始化创建静态实例对象。
    12     }
    13     
    14     private static class Instance{    
    15         private static final Singleton4 singleton=new Singleton4();
    16     }
    17 }

     方法六:Enum枚举实现

       在《Effective Java》中提到,从Java1.5发行版本起,只需编写一个包含单个元素的枚举类型,实现Singleton。

     1 /**
     2  * 枚举类型实现单例
     3  * @author Administrator
     4  *
     5  */
     6 public class Singleton5 {
     7     public enum Singleton{
     8         INSTANCE;
     9         
    10         public void method(){
    11             //任意方法
    12         }
    13     }    
    14 
    15     public static void main(String[] args) {
    16         Singleton.INSTANCE.method();
    17     }
    18 }

      枚举方法 简洁明了,并提供序列化机制,不会在反序列化实例时创建新实例(在其他单例方法中如果要变成可序列化的,不仅要加

    上implements Serializable,还需要提供readResolve方法,参加《effective Java》)。也不会受到反射机制的影响,是实现单例

    模式的最佳方法。

    03 梳理概念点

      反射机制破坏单例模式:通过反射机制 例如借助AccessibleObject.setAccessible方法调用私有构造方法。如果要防止这种机制的

    影响,需要在构造方法中添加判断如果创建第二个实例则抛出异常。

    04 感谢

      [1] Robin Hu的专栏   http://blog.csdn.net/hudashi/article/details/6949379

      [2] cantellow  http://cantellow.iteye.com/blog/838473

      [3] junJZ_2008  http://jiangzhengjun.iteye.com/blog/652440

      [4] zhoujy http://my.oschina.net/zhoujy/blog/134958

      [5] FinderCheng  http://devbean.blog.51cto.com/448512/203501/

        [6] 炸斯特  http://blog.csdn.net/jason0539/article/details/23297037

      [7] Joshua Bloch 著,杨春花,俞黎敏 译 《Effective Java中文版》

  • 相关阅读:
    MySQL—2、B-Tree,B+Tree,聚集索引,非聚集索引
    transient关键字的作用及使用方法
    通过Executors创建线程池和注意小点
    @Validated校验
    Elasticsearch-head插件的安装与配置
    bayaim_java_入门到精通_听课笔记bayaim_20181120
    bayaim_hadoop2_hdfs_20181107
    bayaim_hadoop1_2.2.0伪分布式搭建
    bayaim_hadoop 开篇 0.0
    bayaim_linux_configure_oracle
  • 原文地址:https://www.cnblogs.com/scecit/p/4853953.html
Copyright © 2011-2022 走看看