首页 > 技术文章 > 第三梦 单例模式

yrml 2018-06-08 11:35 原文

初识单例

单例模式,算是我们代码中经常遇见的设计模式之一了。当然我们也上手很快,但是其中的坑也不少,不好好研究一下,这些坑还真不好跳过去。单例简单分分别为懒汉模式、饿汉模式,那我们就从懒汉模式开始吧。

懒汉模式(线程非安全)

这里定义一个私有的全局变量singletonPattern,然后通过一个公有的静态方法对singletonPattern进行判空,如果为空,就new一个类对象出来,然后返回该对象。该种方式可以实现类对象在使用的时候才创建,也就是延时加载。

 1 public class SingletonPattern {
 2 
 3     private static SingletonPattern singletonPattern = null;
 4 
 5     private SingletonPattern() {
 6     }
 7 
 8     public static SingletonPattern getInstance(){
 9         // if这里存在竞态条件
10         if(singletonPattern == null){
11             singletonPattern = new SingletonPattern();
12         }
13         return singletonPattern;
14     }
15 }

懒汉模式(线程安全、低效)

一种比较简单的方式,是同步获取实例化的方法getInstance(),也就是加上synchronized关键字。当然这种方式是非常低效的(jdk后面的版本对synchronized关键字段的底层代码做了很强的优化,所以也不是不可以考虑),具体如下:

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

 懒汉模式(线程安全、高效)

1、双重锁校验DCL(半成品,问题代码,面试考点),注意看下面罗列的四步。

 1 public class SingletonPatternDcl {
 2 
 3   private static SingletonPatternDcl singletonPatternDcl = null;
 4 
 5   private SingletonPatternDcl() {
 6   }
 7 
 8   public static SingletonPatternDcl getInstance(){
 9     if(singletonPatternDcl == null){                         //1、在实例化的情况下,不需要执行加锁动作,性能提高
10       synchronized (SingletonPatternDcl.class){              //2、对类上锁,多个线程的情况下,只有一个线程能够创建对象
11         if(singletonPatternDcl == null){                     //3、实例化对象为空的情况下创建对象
12           singletonPatternDcl = new SingletonPatternDcl();   //4、创建对象
13         }
14       }
15     }
16     return singletonPatternDcl;
17   }
18 }

2、完美的DCL。上面的DCl看起来是非常完美的,所有的逻辑都考虑到了,但是上面的第四步singletonPatternDcl = new SingletonPatternDcl()创建对象的过程其实并非是一个原子操作,这就导致了问题的产生。我们来分析一下第四步在JVM中具体做了哪些事情:

  • a、给singletonPatternDcl分配内存空间
  • b、调用SingletonPatternDcl的构造函数来初始化该成员变量
  • c、将singletonPatternDcl对象指向a步骤分配的内存空间(这一步执行完之后,singletonPatternDcl就为非null了)

而在JVM的即时编译器中存在指令重排序的优化,如果c步骤在b步骤之前执行的话:b执行了,singletonPatternDcl不为空了,第二个线程来了,发现singletonPatternDcl已经不为null了,然后直接返回。但是其实这个时候singletonPatternDcl只是一个内存地址,根本还没有初始化,程序就理所当然的报错了。解决的方法很简单,基于volatile解决方案,如下所示:

   private static volatile SingletonPatternDcl singletonPatternDcl = null;                                                                       

volatile的特性禁止指令重排序,保证了上述a、b、c一定会按着abc的顺序执行,也就避免了上述产生问题的场景。

 饿汉模式(天然的线程安全)

利用类加载的机制,我们可以在类一开始加载的时候就初始化一个实例对象。缺点是无法实现懒加载,并且在某些需要使用动态参数的情况下无法使用。

 1 public class SingletonPatternSafe {
 2 
 3   private static SingletonPatternSafe singletonPatternSafe = new SingletonPatternSafe();
 4 
 5   private SingletonPatternSafe() {
 6   }
 7 
 8   public SingletonPatternSafe getInstance() {
 9     return singletonPatternSafe;
10   }
11 }

这里加上final也是可以的

private static final SingletonPatternSafe singletonPatternSafe = new SingletonPatternSafe();

静态内部类(天然的线程安全)

这种方式的单例实现,也是基于JVM本身机制保证了线程安全。其内部类Holder只有getInstance()方法可以访问。读取的实例的时候也不需要进行同步,没有性能的损失。

 1 public class SingletonPatternHolder {
 2 
 3   private static class Holder {
 4     private static final SingletonPatternHolder INSTANCE = new SingletonPatternHolder();
 5   }
 6 
 7   private SingletonPatternHolder() {
 8   }
 9 
10   public static SingletonPatternHolder getInstance(){
11     return Holder.INSTANCE;     //懒汉式的,只有访问getInstance()方法的时候才实例化
12   }
13 
14 }

枚举方式(绝对的线程安全)

枚举实现单例模式有三个特性:自由序列化、线程安全、保证单例。

  • enum的实现是通过继承了Enum类来实现的,enum结构不能作为子类来继承其他类,但是可以用来实现接口类;
  • 由于enum内部的实现方式其实是final类型的,所以enum类不可以被继承;
  • enum有且仅有private构造器,防止外部的额外构造,这恰好和单例模式相符合;
  • 其内部也是枚举量未被初始化,之后会在静态代码中进行初始化,这就非常类似饿汉模式;
  • 对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,所以Java在序列化和反序列化枚举时做了特殊规定,枚举的writeObject、readObject、readReplace和readResolve等方式是被编译器禁止的,因此不存在实现序列化接口之后调用readObject会重新创建的心得对象从而破坏单例的问题。

基于上述描述,我们发现enum的方式来构造单例模式,代码实现起来非常的简单、自由序列化。并且也是线程的安全,相比起来应该更优选择

1 public enum  SingletonPatternEnum {
2 
3   /**
4    * 实例化对象
5    */
6   INATANCE
7 }

代码实例

我的代码放在GitHub,小伙伴可以作为一个参考、

参考博文

SingletonPattern、我们平常用到的一个设计模式,有必要深入学习,掌握精髓,在实战中灵活运用。感谢前辈们的分享做为引路人。-------书山有路、人儿需行<<

推荐阅读