[Design Patterns] 设计模式(一) 单例模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/FightFightFight/article/details/81879677

概述

单例模式,是指某个类只允许生成一个实例,这个实例由自己实例化并提供给外界一个全局访问点。

使用场景

一些比较消耗资源的类,如网络请求、线程池、缓存等,为了避免创建多个对象消耗更多的资源,需要采用单例模式。

实现方式

1.饿汉式

package com.jyq.instance;

public class HeadTeacher extends Teacher {

    //创建一个私有的、静态的、final的全局对象
    private static final HeadTeacher mHandTeacher = new HeadTeacher();

    //构造函数私有化
    private HeadTeacher() {
    }

    //通过静态方法返回该对象实例
    public static HeadTeacher getInstance() {
        return mHandTeacher;
    }
}

当JVM加载该类时,就马上创建该类唯一的实例,因此得名饿汉式(eagerly instance).饿汉式也是实现单例模式的推荐方式。

2.懒汉式

package com.jyq.instance;

public class HeadTeacher extends Teacher {

    //声明一个私有的、静态的全局对象
    private static HeadTeacher mHandTeacher;

    //构造函数私有化
    private HeadTeacher() {
    }

    //通过静态方法返回该对象实例
    public static HeadTeacher getInstance() {
        if (mHandTeacher == null) {
            mHandTeacher = new HeadTeacher();
        }
        return mHandTeacher;
    }
}

由于只有在第一次调用getInstance()后才会创建该类唯一实例,因此得名懒汉式(lazy instance),这种实例化方式也叫作延时初始化。

改进懒汉式

然而,懒汉式的缺陷显而易见,如果存在多个线程同时访问getInstance()方法,则很有可能返回多个实例,这显然违背了单例模式的初衷。因此,需要解决多线程安全问题,通过synchronized关键字进行同步:

    public static synchronized HeadTeacher getInstance() {
        if (mHandTeacher == null) {
            mHandTeacher = new HeadTeacher();
        }
        return mHandTeacher;
    }

现在getInstance()变成了同步方法,因此,解决了多线程安全问题。

然而,虽然通过同步的方式解决了多线程安全问题,但是又引入了一个问题:每次不同的线程调用getInstance()都会进行同步,同步一个方法必然会造成执行效率下降。

因此,如果对于性能要求不高,则可以使用这种方式,如果getInstance()使用频率高,那么就需要考虑其他方式实现了。

3.Double Check Locking(DCL)

在懒汉式中说到,每次调用getInstance()方法对性能影响很大,实际上,只需要在第一次执行getInstance()方法时进行同步即可:

public class HeadTeacher extends Teacher {

    private static volatile HeadTeacher mHandTeacher;

    //构造函数私有化
    private HeadTeacher() {
    }

    //通过静态方法返回该对象实例
    public static  HeadTeacher getInstance() {
        if (mHandTeacher == null) {
            synchronized (HeadTeacher.class) {
                if (mHandTeacher == null) {
                    mHandTeacher = new HeadTeacher();
                }
            }
        }
        return mHandTeacher;
    }
}

这种实现方式就称为双重检查锁,相比于懒汉式,这种方式避免了在唯一实例被初始化后访问该对象的开销,在第一检查时没有使用同步锁,第二次检查时使用同步锁,从而保证了只有在第一次实例化该类的唯一对象时才会进行同步,大大提高了程序性能。

volatile关键字可以保证任何一个线程读取对象的时候得到最近被写入的值。

不过这种方式也有其缺点,在JDK1.5之前,由于volatile关键字的语义不够强,会导致DLC的不稳定从而失效。在JDK1.5之后,引入了JMM(java 内存模型)解决了这个问题。同时volatile关键字也会或多或少地影响性能。

4.静态内部类单例模式

这种方式示例如下:

package com.jyq.instance;

public class HeadTeacher extends Teacher {

    //构造函数私有化
    private HeadTeacher() {
    }

    //通过静态方法返回该对象实例
    public static HeadTeacher getInstance() {
        return HeadTeacherHolder.mInstance;

    }
    private static class HeadTeacherHolder {
        private static final HeadTeacher mInstance = new HeadTeacher();
    }
}

这种初始化方式也叫作Initialize-On-Demand Holder Class and Singletons模式,当第一次加载HeadTeacher 类时,并不会实例化唯一实例,只有在调用getInstance()时,唯一实例才会被初始化。

5.使用单元素枚举

枚举类型是指由一组固定常量组成的合法值的类型。除了不能继承枚举之外,基本上可以将enum看做一个常规类,因此,我们可以在枚举中添加构造方法、成员方法。同时,enum还有以下特点:

  • 1.线程安全;
  • 2.实现了Serializeable接口,自动提供了序列化机制,,任何情况下都保证了对象的唯一性。

为什么说枚举类型在任何情况下都保证了对象的唯一性呢?这是因为无论采用以上何种方式获取唯一实例,在对该实例进行序列化和反序列化操作后,都会生成新的实例,而如果要保证在任何情况下都维持唯一实例,除了implements Serializable之外,还需要提供readResolve()方法:

    private Object readResolve {

        return mInstance;
    }

使用单元素枚举方式示例如下:

public enum HeadTeacher {
    INSTANCE;

    public void show() {
        System.out.println("Enum show...");
    }
}

不仅需要更少的代码量,而且目前单元素枚举类型已经成为实现单例模式的最佳方法。

总结

  • 1.懒汉式、DLC模式、静态内部类模式,实际上都是表现为一种行为:延迟初始化(Lazy Initialization)。应尽可能少得使用延时初始化,它是一把双刃剑,好处是降低了创建实例的开销,坏处是当在多线程的情况下,必须细致地保证多线程安全问题。
  • 2.单元素的枚举类型是实现单例模式的最佳方式。

猜你喜欢

转载自blog.csdn.net/FightFightFight/article/details/81879677