设计模式初解——单例模式

177 阅读5分钟

一、什么是单利模式

单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。

许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。


单例的实现主要是通过以下两个步骤

  1. 将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
  2. 在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。

二、单例模式的实现方式

饿汉式

public class Singleton {
    //创建一个静态对象
    private static Singleton singleton = new Singleton();
    //私有构造方法
    private Singleton(){}

    //共有的访问接口
    public static Singleton getInstance() {
        return singleton;
    }
}

测试: 输出结果: 设计原理:
我们知道,类加载的方式是按需加载,且加载一次。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。

  • 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
  • 缺点:在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

懒汉式

public class LayzSingleton {
    private static LayzSingleton instance = null;

    private LayzSingleton(){}

    public static LayzSingleton getInstance() {
        if (instance == null) {
            instance = new LayzSingleton();
        }
        return instance;
    }
}

测试: 输出结果: 设计原理: 我们从懒汉式单例可以看到,单例实例被延迟加载\color{red}{延迟加载},即只有在真正使用的时候才会实例化一个对象并交给自己的引用。

  • 优点:懒加载启动快,资源占用小
  • 缺点:线程不安全

线程安全的懒汉式

public class SynchronizedSingleton {
    private static SynchronizedSingleton instance = null;

    private SynchronizedSingleton(){}

    private synchronized SynchronizedSingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedSingleton();
        }
        return instance;
    }
}

设计原理: 懒汉式虽然消除了饿汉式的资源浪费问题,但是却带来了线程安全问题,在if(instance == null)代码处,如果一个线程运行到此处还没结束时,另一个线程也运行到了这里,两个线程继续运行就都创建了单利对象,这样单利对象就不止一个了。解决思路就是给getInstance()方法加同步锁,这样就是线程安全的了

  • 优点:懒加载启动快、资源占用小、线程安全
  • 缺点:synchronized 为独占排他锁,并发性能差。即使在创建成功以后,获取实例仍然是串行化操作

双重加锁懒汉式

public class DoubleCheckSingleton {

    private volatile static DoubleCheckSingleton instance = null;

    private DoubleCheckSingleton(){}

    public static DoubleCheckSingleton getInstance() {
        //先检查实例是否存在,如果不存在才进入下面的同步块
        if (instance == null) {
            //同步代码块,线程安全的创建实例
            synchronized (DoubleCheckSingleton.class) {
                //再次检查实例是否存在,如果不存在才真的创建实例
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

设计原理: 使用volatile关键字生命单利对象,保证了变量的可见性,如果值发生了变更,其他线程立马可见,避免出现脏读的现象,同时使用了synchronized同步代码块,细粒度的控制线程创建单利对象,更加安全,同时性能比synchronized方法更好。 优点:懒加载,线程安全。
注意:实例必须有 volatile 关键字修饰,其保证初始化完全。

静态内部类方式(Holder模式)

public class StaticClassSingleton {
    /**
     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
     */
    private static class SingletonHolder{
        private static StaticClassSingleton instance = new StaticClassSingleton();
    }

    private StaticClassSingleton(){}

    public static StaticClassSingleton getInstance() {
        return SingletonHolder.instance;
    }
}

优点:懒加载、线程安全 缺点:存在序列化和反序列化导致生成两个不同的实例

枚举实现

public enum  EnumSingleton {
    //实例
    INSTANCE;
}

测试: 输出结果: 优点:

  • 自由序列化;
  • 保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量);
  • 线程安全

三、总结

单例模式概念:

  • 全局共享,独一份
  • 构造函数不暴露(如果暴露便不能保证一份),自己负责自己的构造;
  • 饿汉式:一开始就申请好,浪费了点资源,但其线程安全。
  • 懒汉式:Lazy load,用到才加载,非线程安全。如何保证线程安全呢:
    • synchronized getInstance()。
    • 双重检查加锁(volatile)。
  • Holder模式:改成静态内部类,由JVM保证线程安全性。
  • 枚举实现:自由序列化、保证只有一个实例、线程安全