6种单例模式实现

来源:互联网 发布:首次提出大数据的时间 编辑:程序博客网 时间:2024/06/05 17:21

普通懒汉式

public class Singleton {    /** 单例对象 */    private static Singleton instance;    /**     * 私有构造方法.     */    private Singleton() {    }    /**     * 静态方法, 用于获取单利对象.     * 如果单例对象未创建, 则创建新单例对象, 否则直接返回该对象.     *     * @return 单例对象.     */    public static Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }}

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

线程安全的懒汉式

public class Singleton {    /** 单例对象 */    private static Singleton instance;    /**     * 私有构造方法.     */    private Singleton() {    }    /**     * 静态方法, 用于获取单利对象.     * 如果单例对象未创建, 则创建新单例对象, 否则直接返回该对象.     *     * @return 单例对象.     */    public static synchronized Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }}

最简单的线程安全的懒汉模式,通过在 getInstance() 方法上添加 synchronized 关键字,保证同一时间仅有一个线程能够执行该代码段,以保证不会出现上面一种方法产生的问题。
然而,这种方法效率很低。每次调用 getInstance() 方法,都将为代码段加锁,同一时间该代码段只能被一个线程访问。然而除了首次调用外,都是不需要同步的,因为 instance 已经被实例化。

Double-Check

public class Singleton {    /** 单例对象 */    private static Singleton instance;    /**     * 私有构造方法.     */    private Singleton() {    }    /**     * 静态方法, 用于获取单利对象.     * 如果单例对象未创建, 则创建新单例对象, 否则直接返回该对象.     *     * @return 单例对象.     */    public static Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                if (instance == null) {                    instance = new Singleton();                }            }        }        return instance;    }}

Double-check 即双重校验,该方法是针对上述方法提出的一种改进方案。
在 getInstance() 方法中,通过不加锁判断 instance 是否实例化。如果没有实例化,再进行加锁、实例化过程,以减少在实例化后调用 getInstance() 方法导致的性能损耗。

缺陷:instance = new Singleton()语句,看起来是一句代码,但实际上不是一个原子操作。它大致做了三件事:
1)给Singleton的实例分配内存
2)调用构造函数,初始化成员字段
3)将insance对象指向分配的内存空间
但是由于Java编译器允许处理器乱序执行,以及各种其他情况,在JDK1.5前上面的第二步和第三步没有办法保证执行顺序。当执行顺序是1-3-2时,如果执行到3后还没执行2.这时候有另一个线程调用了getInstance()方法,那么它判断到的instance不是null,它不需要经过同步代码块,直接获取到了这个错误的对象去做事去了。这就导致了错误出现。解决方法是private static Singleton instance;改为private static volatile Singleton instance;以及使用JDK1.5以上的版本。建议还是使用静态内部类的方式更好,可以规避上述问题

饿汉式

public class Singleton {    /** 单例对象, 类装载时进行实例化. */    private static final Singleton singleton = new Singleton();    /**     * 私有构造方法.     */    private Singleton() {    }    /**     * 静态方法, 用于获取单利对象.     *     * @return 单例对象.     */    public static Singleton getInstance() {        return singleton;    }}

饿汉式单例的原理是 ClassLoader 装载类是单线程,通过这种机制避免了线程同步问题。
这种方式虽然避免了线程同步问题,但却有可能带来性能问题。
无论该类是否被使用, ClassLoader 都有可能(也有可能被 ClassLoader 忽略)加载该类并实例化该单例对象。所以在基础类库场景下,这种方法会无故消耗更多的资源。

静态内部类方式

public class Singleton {    /**     * 私有构造方法.     */    private Singleton() {    }    /**     * 静态方法, 用于获取单利对象.     *     * @return 单例对象.     */    public static Singleton getInstance() {        return SingletonHolder.instance;    }    private static class SingletonHolder {        /** 单例对象, 类装载时进行实例化. */        private static final Singleton instance = new Singleton();    }}

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

枚举方式

public enum Singleton {   INSTANCE; }
0 0