5种常见的单例模式

Posted atseas

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了5种常见的单例模式相关的知识,希望对你有一定的参考价值。

1.懒汉式(使用时构建对象)

  1.1 版本1:直接私有化构造函数。

        缺点:非线程安全,当多个线程同时运行到if (instance==null)时,会创建多个对象。

 1 public class Singleton {
 2     private static Singleton instance;
 3     private Singleton(){
 4 
 5     }
 6     public static Singleton getInstance(){
 7         if(instance==null){
 8             instance=new Singleton();
 9         }
10         return instance;
11     }
12 }

   

  1.2 版本2: synchronized版本,使用同步锁。

        缺点:造成其他线程全在等待,影响程序执行效率。

 1  public class Singleton {
 2      private static Singleton instance;
 3      private Singleton(){
 4  
 5      }
 6      public static synchronized Singleton getInstance(){
 7          if(instance==null){
 8              instance=new Singleton();
 9          }
10          return instance;
11      }
12  }

 

2.双重校验锁 

  优点:减少加锁的代码块,当问题发生时才去处理。

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

  

注:这段代码在JDK1.5以下会有问题。 
    假设现在有线程A,和线程B两个线程。 
    1.线程A进入getInstance方法,走到代码2处,判断为null,进入同步块。 
    2.线程A走到代码3处,判读为null。 
    3.线程A走到代码4处,执行new操作,在SingletonDoubleCheck还未实例化完成时,使intance为非null。 
    4.线程B进入getInc方法,走到代码2处,判读非null,返回。 
     这时候线程B代码中inatance实例引用的是一个不完整的对象。 
     原因就在于对象的new操作并不是一个原子性操作,它分为3步:

1,给 instance 分配内存
2,调用 SingletonDoubleCheck 的构造函数来初始化成员变量
3,将SingletonDoubleCheck对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

     

 由于JVM的指令重排序(happen-before)的原因,线程B拿到的是初始化未完成的对象,此时会报错。因此我们在代码1处加上关键字volatile

private static volatile Singleton instance;

 注:volatile屏蔽指令重排序的语义在JDK1.5中才被完全修复,此前的JDK中即使将变量声明为volatile也仍然不能完全避免重排序所导致的问题(主要是volatile变量前后的代码仍然存在重排序问题),这点也是在JDK1.5之前的Java中无法安全使用DCL(双锁检测)来实现单例模式的原因。

3.饿汉式(类加载时构造对象)

  优点:类装载的过程由类加载器(ClassLoader)执行,JVM保证线程的同步,避免了有多线程引起的问题。

  缺点:开发者难以把握初始化时机:

     1.加载早会造成资源浪费。

     2.如果初始化依赖其他数据,难以保证其他数据在加载前就已准备好。

  适用:所需单例占用资源少,不依赖其他数据。 

public class Singleton{
       private static  final Singleton INATANCE =new Singleton();
    
       private Singleton (){
    }
       public static Singleton getInatance(){
            return INATANCE;
    }
}        

 

4.静态内部类(延迟加载)

   优点:类装载的过程由类加载器(ClassLoader)执行,JVM保证线程的同步,避免了有多线程引起的问题。

      在使用时才进行实例化,避免资源的浪费。

 public class Singleton {  
      private static class SingletonHolder {  
      private static final Singleton INSTANCE = new Singleton();  
      }  
      private Singleton (){}
      public static final Singleton getInstance() {  
          return SingletonHolder.INSTANCE;  
      }  
 }  

 

5.枚举

    优点:1.线程安全。2.防止反序列化创建对象。

    缺点:不能继承,暂时用的人比较少,不合群。

public Enum Singleton{
    INSTANCE;
    public void Method(){
    }      
}

 

文章仅供参考,欢迎批评指正。








以上是关于5种常见的单例模式的主要内容,如果未能解决你的问题,请参考以下文章

线程安全的单例模式的几种实现

性能比较好的单例写法

Java面试必备:手写单例模式

你见过这样的单例模式吗?

java中的单例模式

月薪5万程序员眼中的单例模式