【面试系列】6种单例模式(Singleton)实现方法比较

转载文章,文章经 LiteCodes 授权,转载至本博客。

原文地址:【面试系列】6种单例模式(Singleton)实现方法比较

下述代码均省略了 Singleton 类的业务代码段,仅表现作为单例所需的代码部分。

懒汉式

普通懒汉式

 1 public class Singleton {
 2  
 3     /** 单例对象 */
 4     private static Singleton instance;
 5  
 6     /**
 7      * 私有构造方法.
 8      */
 9     private Singleton() {
10     }
11  
12     /**
13      * 静态方法, 用于获取单利对象.
14      * 如果单例对象未创建, 则创建新单例对象, 否则直接返回该对象.
15      *
16      * @return 单例对象.
17      */
18     public static Singleton getInstance() {
19         if (instance == null) {
20             instance = new Singleton();
21         }
22         return instance;
23     }
24 }

最简单的懒汉式单例,在首次调用  getInstance();  时,会对单例对象进行实例化。

然而,这种方式明显无法在多线程模式下正常工作。当线程并发调用getInstance();  时,由于线程之间没有进行同步,有可能两个线程同时进入 if 条件,导致实例化两次。

线程安全的懒汉式

 1 public class Singleton {
 2  
 3     /** 单例对象 */
 4     private static Singleton instance;
 5  
 6     /**
 7      * 私有构造方法.
 8      */
 9     private Singleton() {
10     }
11  
12     /**
13      * 静态方法, 用于获取单利对象.
14      * 如果单例对象未创建, 则创建新单例对象, 否则直接返回该对象.
15      *
16      * @return 单例对象.
17      */
18     public static synchronized Singleton getInstance() {
19         if (instance == null) {
20             instance = new Singleton();
21         }
22         return instance;
23     }
24 }

最简单的线程安全的懒汉模式,通过在 getInstance()  方法上添加 synchronized  关键字,保证同一时间仅有一个线程能够执行该代码段,以保证不会出现上面一种方法产生的问题。

然而,这种方法效率很低。每次调用  getInstance()  方法,都将为代码段加锁,同一时间该代码段只能被一个线程访问。然而除了首次调用外,都是不需要同步的,因为 instance  已经被实例化。

Double-Check

 1 public class Singleton {
 2 
 3     /** 单例对象 */
 4     private static volatile Singleton instance;
 5 
 6     /**
 7      * 私有构造方法.
 8      */
 9     private Singleton() {
10     }
11 
12     /**
13      * 静态方法, 用于获取单利对象.
14      * 如果单例对象未创建, 则创建新单例对象, 否则直接返回该对象.
15      *
16      * @return 单例对象.
17      */
18     public static Singleton getInstance() {
19         if (instance == null) {
20             synchronized (Singleton.class) {
21                 if (instance == null) {
22                     instance = new Singleton();
23                 }
24             }
25         }
26         return instance;
27     }
28 }

Double-check 即双重校验,该方法是针对上述方法提出的一种改进方案。

在  getInstance()  方法中,通过不加锁判断 instance  是否实例化。如果没有实例化,再进行加锁、实例化过程,以减少在实例化后调用 getInstance()  方法导致的性能损耗。

需注意的是,在代码第4行, instance  的定义处,添加了 volatile  关键字。

为了保证 instance  在多个线程间同步,需要通过 volatile  关键字,指明该变量的值每次需要从主存中直接获取,避免从线程内存中获取,以保证线程间同步。

饿汉式

 1 public class Singleton {
 2  
 3     /** 单例对象, 类装载时进行实例化. */
 4     private static final Singleton singleton = new Singleton();
 5  
 6     /**
 7      * 私有构造方法.
 8      */
 9     private Singleton() {
10     }
11  
12     /**
13      * 静态方法, 用于获取单利对象.
14      *
15      * @return 单例对象.
16      */
17     public static Singleton getInstance() {
18         return singleton;
19     }
20 }

饿汉式单例的原理是 ClassLoader  装载类是单线程,通过这种机制避免了线程同步问题。

这种方式虽然避免了线程同步问题,但却有可能带来性能问题。

无论该类是否被使用, ClassLoader  都有可能(也有可能被 ClassLoader  忽略)加载该类并实例化该单例对象。所以在基础类库场景下,这种方法会无故消耗更多的资源。

静态内部类方式

 1 public class Singleton {
 2  
 3     /**
 4      * 私有构造方法.
 5      */
 6     private Singleton() {
 7     }
 8  
 9     /**
10      * 静态方法, 用于获取单利对象.
11      *
12      * @return 单例对象.
13      */
14     public static Singleton getInstance() {
15         return SingletonHolder.instance;
16     }
17  
18     private static class SingletonHolder {
19  
20         /** 单例对象, 类装载时进行实例化. */
21         private static final Singleton instance = new Singleton();
22     }
23 }

这种方法同样利用了 ClassLoader  单线程装载的方式,避免了线程同步问题。然而他和上面一种方法不同的地方在于, instance  对象只有在 SingletonHolder  类被装载的时候才会被实例化。也就是说,只有当 getInstance()  方法调用时,才会被实例化,这样就避免了上述的资源损耗。

枚举方式

1 public enum Singleton {
2     INSTANCE;
3 }

在《Effective Java》一书中,Joshua Bloch 推荐使用这种方式实现单例模式。这种方式不仅能够避免线程同步问题,而且由于其语法级的约束,JVM 级的支持,保证了其极强的正确性。

如何选择

俗话说,No silver bullet,每一种实现都有其适用的场景。那么,我们如何选择单例的实现方式呢?答案是:取决于你所期望的内容。

如果你的单例类应用频繁,从系统启动后就需要使用,那么,饿汉式可能是一个不错的选择。类加载过程便已经完成了实例化的单例,在之后的调用过程中,无需再进行实例化,也无需害怕因为线程同步导致的性能损耗。

如果你的单例类占用较多资源,并且调用频率较低,那么或许 Double-Check 的懒汉式是一个不错的选择。在单例使用前,并不会被实例化,其所需要的资源也并不会被占用。

如果你的单例类属于某一个类库,或许 Double-Check 的懒汉式是一个不错的选择。一个功能丰富的类库中,并非所有的类都会被使用。然而 ClassLoader 的加载机制,并不一定会将其排除至外。所以,一个懒汉式的单例有可能降低类库使用者的资源损耗。

……

根据你的应用场景,选择一个合适的单例模式吧~

posted @ 2016-02-16 23:28  Rainisic  阅读(472)  评论(0编辑  收藏  举报