单例模式
什么是单例模式
一个对象在应用程序中只存在一个。
单例实现原理
以java为例,私有构造器,一个私有的静态的本身实例,一个对外开放获取本身实例的静态公共方法。
多种不同的方式实现单例
饿汉式
在使用这个类时就初始化这个对象。
package com.lxlyq.singlePattern; /** * 饿汉式单例 */ public class HungrySingle { // 私有对象 private static HungrySingle hungrySingle = new HungrySingle(); // 私有构造,防止外部new private HungrySingle(){} // 外部调用 public static HungrySingle getHungrySingle() { return hungrySingle; } }
缺点:可能没有使用这个对象,但应用程序中也存在这个对象,浪费内存。
懒汉式
只有获取这个实例时才初始化对象。
package com.lxlyq.singlePattern; /** * 懒汉式 */ public class LazySingle { // 私有对象 未初始化 private static LazySingle lazySingle; // 私有构造 防止外部通过 new 创建 private LazySingle() { } // 获取实例 public static LazySingle getInstance() { // 不存在则创建 if (lazySingle == null) { lazySingle = new LazySingle(); } return lazySingle; } }
优点:弥补了饿汉式的不足。
缺点:当存在多线程的时候将存在线程安全问题,两个线程同时进入if判断语句中,将会存在两个对象。
懒汉式同步方法锁
package com.lxlyq.singlePattern; /** * 懒汉式(同步方法) */ public class LazySynchronizedMethodSingle { // 私有对象 未初始化 private static LazySynchronizedMethodSingle lazySingle; // 私有构造 防止外部通过 new 创建 private LazySynchronizedMethodSingle() { } // 获取实例 加上同步锁 防止多线程创建多个实例 public static synchronized LazySynchronizedMethodSingle getInstance() { // 不存在则创建 if (lazySingle == null) { lazySingle = new LazySynchronizedMethodSingle(); } return lazySingle; } }
优点:当获取时才创建实例,不存在线程安全问题。
缺点:每次获取实例时都需要加锁,效率极低。
懒汉式同步块锁
package com.lxlyq.singlePattern; /** * 懒汉式同步块 */ public class LazySynchronizedBlockSingle { // 私有对象 未初始化 private static LazySynchronizedBlockSingle lazySingle; // 私有构造 防止外部通过 new 创建 private LazySynchronizedBlockSingle() { } // 获取实例 加上同步块防止多线程创建多个实例 public static LazySynchronizedBlockSingle getInstance() { synchronized (LazySynchronizedBlockSingle.class) { // 不存在则创建 if (lazySingle == null) { lazySingle = new LazySynchronizedBlockSingle(); } } return lazySingle; } }
与同步方法锁一样,效率也差不多,每次都需要加锁。需要改进如下
懒汉式双重校验同步块锁
package com.lxlyq.singlePattern; /** * 懒汉式双重校验同步块 */ public class LazyDoubleCheckSynchronizedBlockSingle { // 私有对象 未初始化 private static LazyDoubleCheckSynchronizedBlockSingle lazySingle; // 私有构造 防止外部通过 new 创建 private LazyDoubleCheckSynchronizedBlockSingle() { } // 获取实例 加上同步块防止多线程创建多个实例 public static LazyDoubleCheckSynchronizedBlockSingle getInstance() { if (lazySingle == null) { // 第一次加锁,以后都不会再进入 synchronized (LazyDoubleCheckSynchronizedBlockSingle.class) { // 不存在则创建 if (lazySingle == null) { lazySingle = new LazyDoubleCheckSynchronizedBlockSingle(); } } } return lazySingle; } }
优点:只有获取对象时才被创建。不存在线程安全问题。因为双重校验,只有当对象未被创建前才会被加锁,效率满足要求。
缺点:不符合审美(瞎扯)。
以上的真的都是单例了吗
// 通过反射破坏
package com.lxlyq.singlePattern; import java.lang.reflect.Constructor; public class SingleDemo { public static void main(String[] args) throws Exception { // 通过懒汉式双重校验获取实例 LazyDoubleCheckSynchronizedBlockSingle single1 = LazyDoubleCheckSynchronizedBlockSingle.getInstance(); System.out.println(single1); // 通过懒汉式双重校验获取实例 LazyDoubleCheckSynchronizedBlockSingle single2 = LazyDoubleCheckSynchronizedBlockSingle.getInstance(); System.out.println(single2); // 通过反射创建对象 Class cls = LazyDoubleCheckSynchronizedBlockSingle.class; Constructor<?> cs = cls.getDeclaredConstructor(); cs.setAccessible(true); LazyDoubleCheckSynchronizedBlockSingle single3 = (LazyDoubleCheckSynchronizedBlockSingle) cs.newInstance(); System.out.println(single3); // out // com.lxlyq.singlePattern.LazyDoubleCheckSynchronizedBlockSingle@4554617c // com.lxlyq.singlePattern.LazyDoubleCheckSynchronizedBlockSingle@4554617c // com.lxlyq.singlePattern.LazyDoubleCheckSynchronizedBlockSingle@74a14482 } }
之前所有的单例创建方式都存在通过反射破坏单例问题。
解决单例破坏问题
package com.lxlyq.singlePattern; /** * 禁止通过反射创建单例 */ public class BanReflexCreateSinge { // 采用饿汉式,但是客户端一样可以通过反射创建 private static BanReflexCreateSinge banReflexCreateSinge = new BanReflexCreateSinge(); // 防止反射 private BanReflexCreateSinge(){ if (banReflexCreateSinge != null) { throw new RuntimeException("禁止一切方法破坏单例"); } } // 外部调用 public static BanReflexCreateSinge getInstance() { return banReflexCreateSinge; } }
通过这种反射,当系统中存在时就会抛出异常。
有一种通过枚举方式创建单例,枚举的底层也是这个原理。
这里没有介绍通过枚举创建单例,是我觉得枚举没有类那么灵活,尽管枚举是创建单例的最好方式,但不是最好的方案。
静态内部内实现单例
package com.lxlyq.singlePattern; /** * 内部类创建单例 */ public class LazyInnerClassSingle { // 防止外部调用 private LazyInnerClassSingle(){} // 静态内部类 private static class LazyInnerClass{ private static LazyInnerClassSingle lazyInnerClassSingle = new LazyInnerClassSingle(); } // 获取实例 public static LazyInnerClassSingle getInstance() { return LazyInnerClass.lazyInnerClassSingle; } }
这种方式,没有加锁,但也是懒汉式,代码简洁,推荐使用。
总结
单例优点:
- 系统中只存在一个,减少内存开支,当一个对象需要被频繁创建销毁时,把它作为单例效果将极其明显。
- 系统多次需要使用一个对象时,把他设为单例可以减少系统开支。(如读取的配置信息)。
- 单例可以防止资源被多重占用,如读写文件。
单例缺点:
- 不适用于变化的对象
- 一般单例的职责都过重,违背了'单一原则'(一个类负责一件事)
- 单例没有抽象层,难以扩展。