单例模式是一种对象创建模式,用于产生一个类的具体事例。使用单例模式可以确保整个系统中单例类只产生一个实例。有下面两大好处:
创建方式:
角色 | 作用 |
---|---|
单例类 | 提供单例的工厂,返回类的单例实例 |
使用者 | 获取并使用单例类 |
类基本结构:
public class HungerSingleton { //1.饿汉式 //私有构造器 private HungerSingleton() { System.out.println("create HungerSingleton"); } //私有单例属性 private static HungerSingleton instance = new HungerSingleton(); //获取单例的方法 public static HungerSingleton getInstance() { return instance; } }
public class Singleton { //2.1简单懒汉式(线程不安全) //私有构造器 private Singleton() { System.out.println("create Singleton"); } //私有单例属性[初始化为null] private static Singleton instance = null; //获取单例的方法 public static Singleton getInstance() { if(instance == null) { //此处instance实例化 //首次调用单例时会进入 达成延时加载 instance = new Singleton(); } return instance; } }
public class Singleton { //2.2简单懒汉式(线程安全) //私有构造器 private Singleton() { System.out.println("create Singleton"); } //私有单例属性[初始化为null] private static Singleton instance = null; //获取单例的方法 将此方法使用synchronized关键字同步 public static synchronized Singleton getInstance() { if(instance == null) { //此处instance实例化 //首次调用单例时会进入 达成延时加载 instance = new Singleton(); } return instance; } }
,使不用每次调用geInstance()方法时候都会去竞争锁?
DCL(Double Check Locking)双重检测 就是这样一种实现方式。
传统DCL:
public class DCLLazySingleton { //3.DCL //私有构造器 private DCLLazySingleton() { System.out.println("create DCLLazySingleton"); } //step1 私有单例属性[初始化为null] volatile 保证内存可见性 防止指令重排 private static volatile DCLLazySingleton instance = null; //获取单例的方法 public static DCLLazySingleton getInstance() { //这里判null 是为了在instance有值时,不进入加锁的代码块,提高代码性能。 if(instance == null) { //缩小锁范围 由于是静态方法方法调用的时候不依赖于实例化的对象 加锁只能使用类 synchronized (DCLLazySingleton.class) { //这里判null 是为了配合volatile解决多线程安全问题 if(instance == null) { instance = new DCLLazySingleton(); } } } return instance; } }
传统DCL(未使用 volatile 或在JDK1.8之前版本)面临的问题:
在线程1初始化对象可能并未完成,但是此时已经instance对象已经不为null。(已经分配了内存,但是构造方法还未执行完【 可能有一些属性的赋值未执行 】)
此时线程2再获取instance 则不为null 直接返回。那么此时线程2获取的则为‘构造方法未执行完的instance对象’。则不能保证线程安全。
解决方式:
参考:
public class StaticSingleton { //私有构造器 private StaticSingleton() { System.out.println("create StaticSingleton!"); } //获取单例的方法 public static StaticSingleton getInstance() { return SingletonHolder.instance; } //静态内部类 持有单例 作为静态属性。 //由于只有在访问属性时才会加载静态类初始化instance。所以实现了懒加载。且由于JVM保证了类的加载为线程安全,所以为线程安全的。 private static class SingletonHolder { //私有单例属性 private static StaticSingleton instance = new StaticSingleton(); } }
public enum EnumSingleton { INSTANCE(); EnumSingleton() { System.out.println("create EnumSingleton"); } }
上述的单例实现方式还是会面临一些特殊情况不能保证唯一实例:
private Object readResolve () { //返回当前对象 return instance; }
由于上述两情况比较特殊,所以没有特别关注。
《Java程序性能优化》 -葛一鸣 等编著