枚举实现单例避免被反射破坏的原因

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了枚举实现单例避免被反射破坏的原因相关的知识,希望对你有一定的参考价值。

参考技术A 主要是因为Construct类中的newInstance方法的一个判断条件
if ((this.clazz.getModifiers() & 16384) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");

如果是枚举类型,就会抛出异常,因此只有枚举才能避免被反射破坏
反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败

单例模式详解
https://www.cnblogs.com/chiclee/p/9097772.html

单例模式_反射破坏单例模式_枚举类_枚举类实现单例_枚举类解决单例模式破坏

转:狂神说Java之彻底玩转单例设计模式

彻底玩转单例模式
参考文章:

单例模式:
简介:
单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。

注意:

1、单例类只能有一个实例。

2、单例类必须自己创建自己的唯一实例。

3、单例类必须给所有其他对象提供这一实例。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。

适用场景:

1.需要生成唯一序列的环境
2.需要频繁实例化然后销毁的对象。
3.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
4.方便资源相互通信的环境

构建步骤:

  • 将该类的构造方法定义为私有方法
    •   这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
  • 在该类内提供一个静态实例化方法
    •   当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,
    •   如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。

优缺点:
优点:

  • 在内存中只有一个对象,节省内存空间;
  • 避免频繁的创建销毁对象,可以提高性能;
  • 避免对共享资源的多重占用,简化访问;
  • 为整个系统提供一个全局访问点。

缺点:

  • 不适用于变化频繁的对象;
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;
  • 如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失;

饿汉式单例模式:
​ 饿汉式:饥饿的人,看到食物就上来抢。对应在代码中,在类加载时就立刻实例化。

public class HungrySingleton {

//假如类中存在这样的空间开辟的操作:
//使用饿汉式时,不管用不用,上来就给你开了再说,造成空间浪费。
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];

//1、私有构造器
private HungrySingleton(){

}
//2、类的内部创建对象
private final static HungrySingleton HUNGRYSINGLE = new HungrySingleton();

//3、向外暴露一个静态的公共方法。 getInstance
public static HungrySingleton getInstance(){
return HUNGRYSINGLE;
}
public static void main(String[] args) {
//单线程:
HungrySingleton instance1 = HungrySingleton.getInstance();
HungrySingleton instance2 = HungrySingleton.getInstance();
System.out.println(instance1 == instance2);//true
System.out.println("------------");
//多线程:
new Thread(()->{
HungrySingleton instance_A = HungrySingleton.getInstance();
System.out.println(instance_A);
//com.kuangstudy.Singleton.HungrySingleton@626213bf
}).start();
new Thread(()->{
HungrySingleton instance_B = HungrySingleton.getInstance();
System.out.println(instance_B);
//com.kuangstudy.Singleton.HungrySingleton@626213bf
}).start();
}
}

 

小结:
优点:

  • 基于 classloader 机制避免了多线程的同步问题,在类装载的时候就完成实例化。避免了线程同步问题==>线程安全。
  • 没有加锁,执行效率会提高。
  • 简单好用。

缺点:
在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
懒汉式单例模式:
非线程安全:

public class LazySingleton {

//1.私有化构造函数
private LazySingleton() {
System.out.println(Thread.currentThread().getName()+" ->OK");
}

//2.创建对象(容器)
private static LazySingleton lazyMan ;

//3.对外提供静态实例化方法,判断在对象为空的时候创建
public static LazySingleton getInstance(){
//用的时候再加载
if (lazyMan == null) {
lazyMan = new LazySingleton();
}
return lazyMan;
}
//测试单线程下懒汉式单例
public static void main(String[] args) {
LazySingleton instance1 = LazySingleton.getInstance();
LazySingleton instance2 = LazySingleton.getInstance();
//实例化两个只出现:main ->OK
System.out.println(instance1);//com.kuangstudy.Singleton.LazySingleton@61bbe9ba
System.out.println(instance2);//com.kuangstudy.Singleton.LazySingleton@61bbe9ba
}
}

小结
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

public class LazySingleton {

//1.私有化构造函数
private LazySingleton() {
System.out.println(Thread.currentThread().getName()+" ->OK");
}

//2.创建对象(容器)
private static LazySingleton lazyMan ;

//3.对外提供静态实例化方法,判断在对象为空的时候创建
public static LazySingleton getInstance(){
//用的时候再加载
if (lazyMan == null) {
lazyMan = new LazySingleton();
}
return lazyMan;
}
//开启多条线程实例化LazySingleton
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
// new Thread(()->{LazySingleton.getInstance();}).start();
new Thread(LazySingleton::getInstance).start();
}
}
//结果实例化超过1个对象:(不唯一)
/*

Thread-0 ->OK
Thread-3 ->OK
Thread-2 ->OK
Thread-1 ->OK

*/
}

线程安全:

public class LazySingletonWithDCL {

//1.私有化构造函数
private LazySingletonWithDCL() {
System.out.println(Thread.currentThread().getName() + " ->OK");
}

//2.创建对象(容器)
// private static LazySingletonWithDCL lazyMan ;
//5.new 不是一个原子性操作:
private volatile static LazySingletonWithDCL lazyMan;

//3.对外提供静态实例化方法
//4.为保证线程安全,需要上锁
public static LazySingletonWithDCL getInstance() {
if (lazyMan == null) {
synchronized (LazySingletonWithDCL.class) {
if (lazyMan == null) {
lazyMan = new LazySingletonWithDCL();
}
}
}
return lazyMan;
}

//开启多条线程实例化LazySingletonWithDCL
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
// new Thread(()->{LazySingletonWithDCL.getInstance();}).start();
new Thread(LazySingletonWithDCL::getInstance).start();
}
}
}

问题:
为什么要synchronized (LazySingletonWithDCL.class)而不是对方法加锁?
相关博客:Synchronized方法锁、对象锁、类锁区别 (精)

简单回答:

synchronized 重量级锁,锁的范围越小越好,class只有一个,而方法会每次都执行,因此为了提高效率,锁对象而不是锁方法。
new对象的过程为什么不是一个原子性操作?
相关博客:new一个对象竟然不是原子操作?

实锤 new 对象不是原子性操作,会执行以下操作:

  • 分配内存空间
  • 执行构造方法,
  • 初始化对象把对象指向空间实践出真知:

这是原子类AtomicInteger.getAndIncrement()方法,反编译后:

public class TestNew {

private static int num = 0;

public static void main(String[] args) {
TestNew testNew = new TestNew();
}
public void add(){
num++;
};
}

反编译后:

 

 

 

 

小结:

  • 双检锁/双重校验锁(DCL,即 double-checked locking)
  • 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。getInstance() 的性能对应用程序很关键。
  • 这样,实例化代码只用执行一次,后面再次访问时,判断 if(lazyMan == null),直接return实例化对象,也避免的反复进行方法同步.
  • 线程安全;延迟加载;效率较高

反射破坏单例:
在Java进阶中学习过非常暴力的获取类的方式:反射(详见:注解与反射)

因此对于在单例模式中私有的方法,我们可以通过反射进行破解:

对DCL饿汉式单例进行破解:
实力诠释再多的

以上是关于枚举实现单例避免被反射破坏的原因的主要内容,如果未能解决你的问题,请参考以下文章

单例模式_反射破坏单例模式_枚举类_枚举类实现单例_枚举类解决单例模式破坏

单例模式--反射--防止序列化破坏单例模式

如何避免单例模式被破坏

枚举单例那些事儿

枚举单例那些事儿

java单例之enum实现方式