单例模式作为一种目标明确、结构简单、理解容易的设计模式,在软件开发中使用频率相当高,在很多应用软件和框架中都得以广泛应用。
有什么用?
有的时候,对于一些对象,我们只需要一个或者说“唯一”,比方说:任务管理器、线程池、缓存、对话框、偏好设置和注册表的对象,日志对象,充当打印机、显卡等设备的驱动程序的对象。事实上,这类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,例如:程序出错、资源使用过量、或者是得到不一致的结果。
虽然很多时候,通过程序员之间做的约束、或者是利用全局变量(Java的静态变量),的确可以保证一个类只存在一个实例。但那样做不一定是最好的,我们需要一个更好的、通用的解决方案。而经过时间的考验,单件模式 可以确保一个对象只有一个实例被创建。单件模式也给了我们一个全局的访问点,和全局变量一样方便,又没有全局变量的缺点。
全局变量的缺点,举例来说,如果将对象赋值给一个全局变量,那么你必须在程序一开始就创建好对象,若这个对象非常的耗费资源,而程序在这次的执行过程中又一直没用到它,不就形成浪费了吗?而利用单例模式,我们可以在需要的时候才创建对象。
其实利用静态类变量、静态方法和适当的访问修饰符,的确也可以做到这一点。但是不管使用哪一种方法,我们都应该了解单例模式的运作方式和具体使用。
单例模式的实现可分为四大类:
第一类,饿汉式(空间换时间)
最简单的实现方式
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
第二类,懒汉式(时间换空间)
线程不安全
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
线程安全
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
双检锁/双重校验锁(DCL,即 double-checked locking)
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
通过volatile关键字防止指令重排序。
volatile关键字不但可以保证线程访问的变量值是主存中的最新值,而且可以防止指令重排。(非最佳实现,据说大部分JVM的implementation并不尊重volatile的规则,并且这个类可以通过反射构造多个实例对象。)
public class Singleton { private Singleton() {} private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
第三类,IoDH(initialization on demand holder)
也可称静态内部类方式,延迟加载且线程安全(任何初始化失败都会导致单例类不可用,也就是说,IoDH这种实现方式只能用于能保证初始化不会失败的情况。)
加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。
需要延迟加载时,推荐使用
public class Singleton { private Singleton (){} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
第四类,枚举
这种实现方式还没有被广泛采用,由于该方式的单例在面对复杂序列化以及反射攻击时,都能防止多次实例化,被称为单例模式的最佳实现。
不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法。
public enum SingletonEnum { INSTANCE; public void doSomething() { } }
总结
- 一个核心原理就是私有构造,并且通过静态方法获取一个实例。
- 在这个过程中必须保证线程的安全性。
- 推荐用静态内部内实现单例,或加了Volatile关键字的双重检查单例(JDK 1.5及以上版本)
参考:
共同学习,共同进步,若有补充,欢迎指出,谢谢!