单例模式(Singleton Pattern) 什么是单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
特点
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
优点
在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
避免对资源的多重占用
缺点 没有接口,不能继承,与单一职责原则 冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
实现 实现单例模式关键点在于构造函数私有化 ,同时又分为饿汉式,懒汉式,枚举等实现方式
饿汉式 根据具体实现方式又分为静态变量,静态常量和静态代码块等
饿汉式(静态变量) 饿汉式(静态常量) 饿汉式(静态代码块) public class Singleton { private static Singleton instance = new Singleton(); private Singleton () { } public static Singleton getInstance () { return instance; } }
public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton () { } public static Singleton getInstance () { return INSTANCE; } }
public class Singleton { private static Singleton instance; static { instance = new Singleton(); } private Singleton () { } public static Singleton getInstance () { return instance; } }
该方式的优点是写法简单,并且是线程安全的,没有加锁,执行效率会提高。
缺点是该对象在类装载载的时候就进行了实例化,没有达到懒加载的目的,容易造成内存浪费。
懒汉式 懒汉式(线程不安全) 懒汉式(线程安全,同步方法) 懒汉式(线程不安全,同步代码块) 懒汉式(线程安全,双重检测锁(DCL)) 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; } }
public class Singleton { private static Singleton instance; private Singleton () { } public static Singleton getInstance () { if (instance == null ) { synchronized (Singleton.class) { instance = new Singleton(); } } return instance; } }
public class Singleton { private static volatile Singleton instance = null ; private Singleton () { } public static Singleton getInstance () { if (instance == null ) { synchronized (Singleton.class) { if (instance == null ) { instance = new Singleton(); } } } return instance; } }
静态内部类 public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton () { } public static final Singleton getInstance () { return SingletonHolder.INSTANCE; } }
枚举 public enum Singleton { INSTANCE; }
破解 反序列化 Singleton.java
import java.io.Serializable;public class Singleton implements Serializable { private static final Singleton INSTANCE = new Singleton(); private Singleton () { } public static Singleton getInstance () { return INSTANCE; } }
DeserializeSingleton.java
import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class DeserializeSingleton { public static void main (String[] args) throws Exception { String path = "本地路径" ; Singleton s1 = Singleton.getInstance(); FileOutputStream fos = new FileOutputStream(path); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s1); oos.close(); fos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path)); Singleton s2 = (Singleton) ois.readObject(); System.out.println(s1); System.out.println(s2); } }
下面是来自ObjectInputStream类的注释
* If the deserialized object defines a readResolve method * and the invocation of that method returns an array, then readUnshared * returns a shallow clone of that array; this guarantees that the returned * array object is unique and cannot be obtained a second time from an * invocation of readObject or readUnshared on the ObjectInputStream, * even if the underlying data stream has been manipulated.
大致意思是如果被反序列化的对象定义了readResolve方法,那么ObjectInputStream的readUnshared将返回一个对象的浅克隆,这保证了对象是唯一的并且不能通过调用ObjectInputStream的方法进行二次获得。
import java.io.ObjectStreamException;import java.io.Serializable;public class Singleton implements Serializable { private static final Singleton INSTANCE = new Singleton(); private Singleton () { } public static Singleton getInstance () { return INSTANCE; } private Object readResolve () throws ObjectStreamException { return INSTANCE; } }
再次调用DeserializeSingleton打印结果
反射 首先要明白一点,只有枚举 可以防止反射。
Singleton.java
package singleton;public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton () { } public static Singleton getInstance () { return INSTANCE; } }
ReflectSingleton.java
package singleton;import java.lang.reflect.Constructor;public class ReflectSingleton { public static void main (String[] args) throws Exception { Singleton s1=Singleton.getInstance(); Singleton s2=Singleton.getInstance(); System.out.println(s1); System.out.println(s2); System.out.println("=============反射破解单例===============" ); Class<?> clazz = s1.getClass(); Constructor<?> constructor = clazz.getDeclaredConstructor(null ); constructor.setAccessible(true ); Singleton s3 = (Singleton) constructor.newInstance(null ); System.out.println(s3); } }
输出结果:
枚举防止反射的原理分析
首先从ReflectSingleton看出,生成新的对象关键在于constructor.newInstance(null),下面是Constructor.newInstance()方法代码
public T newInstance (Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null , modifiers); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0 ) throw new IllegalArgumentException("Cannot reflectively create enum objects" ); ConstructorAccessor ca = constructorAccessor; if (ca == null ) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
那么枚举类的修饰符是什么呢?
创建枚举类
public enum Singleton { INSTANCE; }
获取类修饰符的值并打印
package singleton;import java.lang.reflect.Modifier;public class EnumModifier { public static void main (String[] args) { int mod = Singleton.class.getModifiers(); String modifiers = Modifier.toString(mod); System.out.println(mod); System.out.println(modifiers); } }
为了验证结果,使用javac 编译Singleton.java,再通过javap反编译Singleton.class(这个反编译结果并非最终结果,详细原因稍后再说 )
PS D:\idea_workspace\design_pattern\src\singleton> javac .\Singleton.javaPS D:\idea_workspace\design_pattern\src\singleton> javap .\Singleton.classCompiled from "Singleton.java" public final class singleton .Singleton extends java .lang .Enum <singleton .Singleton > { public static final singleton.Singleton INSTANCE; public static singleton.Singleton[] values (); public static singleton.Singleton valueOf(java.lang.String); static {}; } PS D:\idea_workspace\design_pattern\src\singleton>
可以看到输出是正确的,那么回到最初,clazz.getModifiers() & Modifier.ENUM的结果是大于0的,当我们使用反射去创建对象的时候,理应会抛出IllegalArgumentException,让我们实验下我们的猜想
import java.lang.reflect.Constructor;public class ReflectSingleton { public static void main (String[] args) throws Exception { Singleton s1=Singleton.INSTANCE; Singleton s2=Singleton.INSTANCE; System.out.println(s1); System.out.println(s2); System.out.println("=============反射破解单例===============" ); Class<?> clazz = s1.getClass(); Constructor<?> constructor = clazz.getDeclaredConstructor(null ); constructor.setAccessible(true ); Singleton s3 = (Singleton) constructor.newInstance(null ); System.out.println(s3); } }
为什么执行结果和我们的预期不一致呢?
这就回到了我提到的javap反编译结果并非最终结果(我还使用JD-GUI查看,结果基本一致),那么最终反编译结果是什么样呢?
这里要介绍一款很老的反编译工具:jad
将jad.exe放在Singleton.class的同级目录下,反编译Singleton.class
D:\idea_workspace\design_pattern\src\singleton>jad -p Singleton.class // Decompiled by Jad v1.5.8 g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3 ) // Source File Name: Singleton.java package singleton; public final class Singleton extends Enum { public static Singleton[] values () { return (Singleton[])$VALUES .clone(); } public static Singleton valueOf(String s) { return (Singleton)Enum.valueOf(singleton/Singleton, s); } private Singleton(String s, int i) { super(s, i); } public static final Singleton INSTANCE; private static final Singleton $VALUES []; static { INSTANCE = new Singleton("INSTANCE" , 0 ); $VALUES = (new Singleton[] { INSTANCE }); } }
可以看到jad反编译的结果里枚举类的构造函数是private Singleton(String s, int i),那么让我们改造ReflectSingleton.java并打印结果
import java.lang.reflect.Constructor;public class ReflectSingleton { public static void main (String[] args) throws Exception { Singleton s1=Singleton.INSTANCE; Singleton s2=Singleton.INSTANCE; System.out.println(s1); System.out.println(s2); System.out.println("=============反射破解单例===============" ); Class<?> clazz = s1.getClass(); Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int .class); constructor.setAccessible(true ); Singleton s3 = (Singleton) constructor.newInstance(String.class, int .class); System.out.println(s3); } }