单例就是只有一个实例对象,即在整个程序中,同一个类始终只有一个对象进行操作。这样可以极大的减少内存开支和系统的性能开销。这种模式提供了一种创建对象的最佳方式,让类负责创建自己的对象,同时确保只有单个对象被创建。这个类需要提供访问其唯一对象的方式,且可以直接访问,不需要实例化该类的对象
- 为保证只能由自己创建对象,单例类必须构造方法私有化
- 单例类必须自己创建自己的唯一实例
- 单例类必须给所有其他对象提供这一实例
- 经过多年的演进,单例模式有诸多实现方式,下面逐个介绍
饿汉式
饿汉式单例是最普通的单例模式写法,由于在类加载时就创建对象,保证了线程的安全。这种方式比较常用,但容易产生垃圾对象,对空间的消耗较大,但是不存在线程安全问题, 因此不用加锁,效率较高,以空间换时间。
public class Singleton {
//单例模式的核心,构造方法私有化
private Singleton() {
}
//用于全局引用的唯一单例对象,在一开始就创建好
private final static Singleton INSTANCE = new Singleton();
//获取全局唯一的单例对象
public static Singleton getInstance() {
return INSTANCE;
}
}
懒汉式
双重检测锁(DCL,即 double-checked locking)懒汉式单例模式
- 添加synchronized关键字规避多线程情况
- INSTANCE = new Singleton(); 不是原子性操作,需要增加 volatile 关键字来避免指令重排
- 分配对象内存空间
- 执行构造方法初始化对象
- 设置 instance 指向刚分配的内存地址,此时 instance != null
public class Singleton {
private Singleton() {
}
//在一开始先不进行对象创建
private volatile static Singleton INSTANCE;
public static Singleton getInstance(){
// 外层检测规避多线程情况
if(INSTANCE == null) {
// 只对赋值这一步进行加锁,提升效率
synchronized (Singleton.class) {
// 内层检测以实现单例
if(INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
静态内部类实现
静态内部类实现依赖于 Java 在类加载时不会加载其内部类,只有实例化内部类时才会加载的特性。其他语言不具备这一特性,无法使用该方式实现,因此这种方式不具备通用性。
public class Singleton {
private Singleton() {
}
/**
* 由静态内部类持有单例对象
* <p>根据类加载特性,仅使用外部类时,不会对静态内部类进行初始化
*/
private static class Holder {
private final static Singleton INSTANCE = new Singleton();
}
/**
* 只有真正使用内部类时,才会进行类初始化
* @return 单例对象
*/
public static Singleton getInstance(){
// 直接获取内部类中的对象
return Holder.INSTANCE;
}
}
利用反射破坏单例
-
反射可以无视 private 修饰的构造方法,可以直接在外面 newInstance,破坏单例
Singleton singleton1 = Singleton.getInstance(); // 获取无参构造器 Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(null); // 取消无参构造器私有属性 declaredConstructor.setAccessible(true); // 使用反射获取的无参构造器实例化对象 Singleton singleton2 = declaredConstructor.newInstance(); // 比较实例化的两个对象 System.out.println(singleton1 == singleton2); //输出结果为 false
-
可以在构造方法中加上对象的非空判断,但如果先用反射创建对象,判断就不生效了
private Singleton() { synchronized (Singleton.class) { if (INSTANCE != null) { throw new RuntimeException("不要试图用反射破坏单例"); } } }
-
使用标志位来避免重复创建:
private static boolean flag = false; private Singleton() { synchronized (Singleton.class) { if (flag == false) { // 第一次创建后,标志位设为 true flag = true; } else { // 后续再使用构造方法创建则直接报错 throw new RuntimeException("不要试图用反射破坏单例模式"); } } }
但还是不能完全防止反射破坏单例,因为可以利用反射修改 flag 的值。
Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); Singleton singleton1 = declaredConstructor.newInstance(); // 获取 flag 属性 Field field = Singleton.class.getDeclaredField("flag"); // 取消 flag 属性的私有属性 field.setAccessible(true); // 通过反射,修改属性的值 field.set(singleton1, false); Singleton singleton2 = declaredConstructor.newInstance(); System.out.println(singleton1 == singleton2); //输出结果为 false
枚举实现
public enum Singleton {
INSTANCE
}
尝试用反射进行破坏:
Singleton instance1 = Singleton.INSTANCE;
Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Singleton instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
运行报错:
Exception in thread "main" java.lang.NoSuchMethodException: cn.sail.singleton.enums.Singleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
查看编译后的 Singleton.class 文件,是存在无参构造的, 再使用jad进行反编译,可以看到,确实是没有无参构造,而是存在一个有参构造
public final class Singleton extends Enum
{
public static Singleton[] values()
{
return (Singleton[])$VALUES.clone();
}
public static Singleton valueOf(String name)
{
return (Singleton)Enum.valueOf(cn/sail/singleton/enums/Singleton, name);
}
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
});
}
}
按照有参构造再使用反射创建:
Singleton instance1 = Singleton.INSTANCE;
Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
Singleton instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
此时运行也报错,但报的错不一样了,这就是希望得到的结果:不能用反射破坏枚举:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
该报错信息是在反射中 Constructor 的 newInstance() 源码,如果反射类为枚举直接抛出该异常:
@CallerSensitive
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;
}
标签:Singleton,模式,INSTANCE,static,单例,declaredConstructor,class
From: https://www.cnblogs.com/bkyxiaozhi/p/16667386.html