单例模式属于创建型设计模式,该类设计模式抽象了对象创建、组织的过程,将目标类的信息进行封装,仅向外部提供获取对象的接口。
单例模式确保系统中只包含一个目标类的实例,负责完成其实例化并向外部提供该实例。一般可用于文件创建、数据库连接等辅助类,实现对系统资源的控制。
一般来说,单例模式在实现上具有私有构造方法、私有静态变量、公有返回接口等特点。以下为几种常见的单例模式实现方法。
饿汉模式在类加载时完成实例的创建,不存在线程安全的问题,缺点在于即使未被引用也始终会存在该类的实例。
class Singleton{ private static final Singleton singleton = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return singleton; } }注意final,保证引用的实例不变。
懒汉模式在初次获取实例时完成创建并返回,可以实现懒加载,但其缺点在于初次获取时可能需要更长的时间;适用于创建消耗大、实例使用少的情况,存在线程安全问题。
class Singleton{ private static Singleton1 instance = null; private Singleton() { } public static Singleton getInstance() { if (null == instance) { instance = new Singleton(); } return instance; } }双重检验锁是适用于多线程模式下的懒汉模式,相比于直接对整个getInstance()方法加锁,可以减少大量进入临界区的性能消耗。
class Singleton{ private static volatile Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if (singleton == null){ synchronized (Singleton.class){ if (singleton == null){ singleton = new Singleton(); } } } return singleton; } }第一层判断是为了在实例已经初始化时直接返回,第二层锁可以防止同时进入临界区的不同线程多次实例化对象。
注意volatile:
由于singleton = new Singleton();并非原子操作,而是由分配内存空间、初始化对象以及将引用指向堆对象三个步骤完成,而在JVM执行时可能产生重排序,使得引用变量指向了未初始化的堆对象,导致另一个线程判断失败(第一层)获取到该未初始化的实例;如下图所示:
volatile关键字可以保证线程之间的可见性及有序性,使所有写操作先于读操作执行,避免上述问题。
线程安全:与懒汉模式相同,利用类的加载保证单例对象只被实例化一次;
懒加载:由于一个类只有在其静态成员初次被引用时才会被加载,即调用Singleton.getInstance()时内部类SingletonHolder才会被加载,同时初始化instance。
反射安全:通过反射不能从外部类中获取内部类的属性,即不可对instance进行修改。
public class Singleton{ private static class SingletonHolder{ public static Singleton instance = new Singleton(); } private Singleton(){} public static Singleton getInstance(){ return SingletonHolder.instance; } }
在Java中,枚举可以看作一个类,其中也可以定义方法;而枚举中的变量可以看作该类的一个实例。枚举的构造方法限制为私有,在初次访问枚举实例时执行;而枚举中的实例定义为static final,从而只可实例化一次。
class SingleTon{} enum SingleEnum{ myEnum; // 枚举实例 private SingleTon singleTon; SingleEnum() { // 构造方法,默认为private singleTon = new SingleTon(); } public SingleTon getSingle(){ return singleTon; } }实例的获取:
SingleTon s=SingleEnum.myEnum.getSingle();