单例模式概念
1.什么是单例模式?
保证整个系统中一个类只有一个对象的实例,实现这种功能的方式就叫单例模式。
2.实现单例模式的思路
1. 构造私有:
如果要保证一个类不能多次被实例化,那么我肯定要阻止对象被new 出来,所以需要把类的所有构造方法私有化。
2.以静态方法返回实例:
因为外界就不能通过new来获得对象,所以我们要通过提供类的方法来让外界获取对象实例。
3.确保对象实例只有一个:
只对类进行一次实例化,以后都直接获取第一次实例化的对象。
1.饿汉式
/** * 单例模式 * 饿汉式 */ public class Singleton1 implements Serializable { // 构造函数 private Singleton1() { System.out.println("开始初始化实例======"); } private final static Singleton1 instance = new Singleton1(); public static Singleton1 getInstance() { return instance; } // 一个其他方法用来测试执行该方法时会不会实例化 public static void otherMethod() { System.out.println("执行其他方法======"); } }
测试饿汉式执行代码,并且通过反射,反序列化,和unsafe类,测试是否可以破坏单例模式
public class TestSingleton { public static void main(String[] args) throws Exception { Singleton1.otherMethod(); System.out.println("======================="); System.out.println(Singleton1.getInstance()); System.out.println(Singleton1.getInstance()); reflection(Singleton1.class); serializable(Singleton1.getInstance()); unsafe(Singleton1.class); } //通过反射破坏单例模式 public static void reflection(Class<?> clazz) throws Exception { Constructor<?> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); System.out.println("反射创建实例:"+constructor.newInstance()); } //通过反序列化破坏实例 public static void serializable(Object instance) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(instance); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); System.out.println("反序列化创建实例:"+ois.readObject()); } //通过unsafe破坏单例 public static void unsafe(Class<?> clazz) throws InstantiationException { Object instance = UnsafeUtils.getUnsafe().allocateInstance(clazz); System.out.println("unsafe 创建实例:"+instance); } }
执行结果如下:
可以看到确实在执行其他方法前,就初始化了该实例,并且也两次调用 getInstance() 方法也成功返回来同一实例,但是可以看到通过反射,反序列化,和unsafe类都可以破坏该单例模式,创建了不同的实例对象.
优化饿汉式的代码,可以解决反射,反序列化的破坏,但是目前没有找到防止unsafe破坏的方法:
/** * 单例模式 * 饿汉式 */ public class Singleton1 implements Serializable { // 构造函数 private Singleton1() { // 防止反射创建对象破坏单例 if (instance != null) { throw new RuntimeException("单例对象不能重复创建"); } System.out.println("开始初始化实例======"); } private final static Singleton1 instance = new Singleton1(); public static Singleton1 getInstance() { return instance; } // 一个其他方法用来测试执行该方法时会不会实例化 public static void otherMethod() { System.out.println("执行其他方法======"); } // 对于实现序列化的对象重写readResolve()方法,防止反序列化破幻单例 public Object readResolve() { return instance; } }
执行结果如下: 执行反射创建时直接抛出异常,反序列化返回的实例也都是同一个实例
2.枚举饿汉式
/** * 枚举饿汉式 */ public enum Singleton2 { INSTANCE; private Singleton2() { System.out.println("开始初始化实例======"); } @Override public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } public static Singleton2 getInstance() { return INSTANCE; } public static void otherMethod() { System.out.println("otherMethod()"); } }
执行结果如下: 枚举饿汉式能天然防止反射、反序列化破坏单例
3.双检锁懒汉式
/** * 双检锁懒汉式 */ public class Singleton3 implements Serializable { private Singleton3() { // 防止反射创建对象破坏单例 if (INSTANCE != null) { throw new RuntimeException("单例对象不能重复创建"); } System.out.println("开始初始化实例======"); } private static volatile Singleton3 INSTANCE = null; // volatile 保证不同线程内存可见性,执行有序性 public static Singleton3 getInstance() { if (INSTANCE == null) { // 加锁保证多线程并发执行 synchronized (Singleton3.class) { if (INSTANCE == null) { INSTANCE = new Singleton3(); } } } return INSTANCE; } public static void otherMethod() { System.out.println("otherMethod()"); } // 对于实现序列化的对象重写readResolve()方法,防止反序列化破幻单例 public Object readResolve() { return INSTANCE; } }
为何必须加 volatile:
-
INSTANCE = new Singleton3()
不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造 -
如果线程1 先执行了赋值,线程2 执行到第一个
INSTANCE == null
时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象
执行结果如下:
可以看到在执行其他方法是并没有进行初始化实例,在调用 getInstance() 时才会进行初始化.
4.内部类懒汉式
/*** * 内部类懒汉式 */ public class Singleton4 implements Serializable { private Singleton4() { // 防止反射创建对象破坏单例 if (Holder.INSTANCE != null) { throw new RuntimeException("单例对象不能重复创建"); } System.out.println("开始初始化实例======"); } private static class Holder { static Singleton4 INSTANCE = new Singleton4(); } public static Singleton4 getInstance() { return Holder.INSTANCE; } public static void otherMethod() { System.out.println("otherMethod()"); } // 对于实现序列化的对象重写readResolve()方法,防止反序列化破幻单例 public Object readResolve() { return Holder.INSTANCE; } }
当外部类被访问时,并不会加载内部类,所以只要不访问Holder这个内部类,static Singleton4 INSTANCE = new Singleton4(); 不会实例化,这就相当于实现懒加载的效果,只有当Singleton4.getInstance() 被调用时访问内部类的属性,此时才会将对象进行实例化,这样既解决了饿汉模式下可能造成资源浪费的问题,也避免了了懒汉模式下的并发问题。
执行结果如下:
JDK 中单例的体现
-
Runtime 体现了饿汉式单例
-
Console 体现了双检锁懒汉式单例
-
Collections 中的 EmptyNavigableSet 内部类懒汉式单例
-
ReverseComparator.REVERSE_ORDER 内部类懒汉式单例
-
Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例