文章首发:设计模式之单例模式
单例模式简介
单例模式可以说是最简单的模式之一,属于创建型模式。单一的类负责创建自己的对象,同时确保该类有且仅有一个对象被创建。同时这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 单例类智能有一个实例
- 单例类必须自己创建自己的唯一实例,外部不能去创建
- 单例类必须提供一个方法来获取该类的对象
单例模式的实现主要有:饿汉式和懒汉式两种。下面将详细讲解这两种实现方法。代码的实现中主要使用lombok工具包。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--springboot工程可省略version-->
<version>1.16.18</version>
</dependency>
饿汉式
@Data //lombok注解
@AllArgsConstructor(access = AccessLevel.PRIVATE) //lombok注解 私有化全参构造方法
public class SingletonHunger {
//关键 可以使用静态变量,也可以使用静态代码块
private static final SingletonHunger INSTANCE = new SingletonHunger();
private String name;
private String age;
//私有化空参构造,无法在其他类直接new对象
private SingletonHunger() {
}
public static SingletonHunger getInstance1(String name, String age) {
INSTANCE.setAge(age);
INSTANCE.setName(name);
return INSTANCE;
}
}
饿汉式就是在类加载的时候就去就去创建对象,主要是static
关键字,static修饰的静态变量或方法在类加载的时候就会被加载到方法区中,只会初始化一次。从static关键字的特性就能很好地理解饿汉式了。对于这个饿汉式可以延伸去了解类的加载,例如java.lang.ClassLoader这个类。
饿汉式是会在类加载的时候就回去实例化对象,虽然可以保证的对象的单例,但是会在一开始就加载很多的对象,一下子内存就上涨了。也有可能加载了不需要的类对象,所以就有了懒汉式。
懒汉式
懒汉式在需要的时候才去实例化对象。即在调用单例类提供方法的时候,采取实例化对象。懒汉式的实现也有很多种。
懒汉式:同步方法--->同步代码块(不怎么推荐)
@Data
public class SingletonLazy {
private volatile static SingletonHunger instance;
private String name;
private String age;
//私有化空参构造,无法在其他类直接new对象
private SingletonHunger() {
}
//最简单、最low的写法,有线程安全的问题
public static SingletonHunger getInstanceV1(String name, String age) {
instance = new SingletonHunger();
instance.setAge(age);
instance.setName(name);
return instance;
}
//改进V1->V2,解决线程安全最简单、最懒和最烂的做法
public synchronized static SingletonHunger getInstanceV2(String name, String age) {
instance = new SingletonHunger();
instance.setAge(age);
instance.setName(name);
return instance;
}
//V3 这个也就那样...
public static SingletonHunger getInstanceV3(String name, String age) {
if(instance==null){
synchronized (SingletonLazy.class) {
instance = new SingletonHunger();
instance.setAge(age);
instance.setName(name);
}
return instance;
}
//上面三种的缺点就不多说了,也不没必要多看。当然还有著名DCL(Double Check Lock),这个可以多讲
public static SingletonLazy getInstanceByDCL(String name, String age) {
//1.对象还没初始化instance为空,当大量的线程"同时到达"一个if时,假设是长时间的大量并发的情况下
//12.假如在第一个线程成功初始化对象的之后(instance不为空),如果还有线程进来,就直接返回instance
if (instance == null) {
//2.到达这里只有一个线程拿到锁---> 8.之前没拿到锁的线程,在第一个线程释放锁后,其余的线程来哄抢
//大量的线程拿不到锁被阻塞(后面的线程就和第二个线程一样的执行流程一样了)
synchronized (SingletonLazy.class) {
//3.再次判空 ---> 9.第二个线程拿到锁的线程再次判空,由于第一个线程已经初始化,instance不为空
if (instance == null) {
//4.第一个拿到锁的线程初始化对象
instance = new SingletonLazy();
instance.setAge(age);
instance.setName(name);
}
}
//5.第一个线程执行完,释放锁 ---> 10.第二个线程释放锁
}
//6.第一个线程得到对象 11.第二线程拿到同样的对象
return instance;
}
}
当然synchronized可以换成Lock,也可以换成自旋锁。自旋锁也是很能装逼,在jdk1.8中的ConcurrentHashMap中就换成了自旋锁。DCL的方法虽然还算可以,但是还有更好的方法。DCL其中还有一个关键就是volatile关键字,就是在instance = new SingletonLazy()
时防止指令重排。volatile和自旋锁可以实现轻量级锁。
静态内部类Holder
下面介绍静态内部内类的懒汉式实现:
@Data
public class SingletonLazy implements Serializable {
private String name;
private String age;
private SingletonLazy() {
}
/**
* 类加载时类加载器操作会加锁(JVM底层实现){@link ClassLoader}
*
* @param name
* @param age
* @return
*/
public static SingletonLazy getInstanceByHolder(String name, String age) {
//调用静态内部内类
return SingletonLazy.InstanceHolder.getInstance(name, age);
}
//静态内部类
private static class InstanceHolder {
private static final SingletonLazy LAZY = new SingletonLazy();
private static SingletonLazy getInstance(String name, String age) {
LAZY.setAge(age);
LAZY.setName(name);
return LAZY;
}
}
}
静态内部内类只有在调用的时候才会初始化。第一次调用SingletonLazy.InstanceHolder.getInstance(name, age)
的时候静态内部类初始化,同时创建外部内的对象。static修饰只会初始化一次。其实静态内部类的原理时利用了ClassLoader的机制,ClassLoader的加载Class的方法有synchronized关键字修饰。在类加载到完成就存在加锁和释放锁的操作。比DCL的方式就少了synchronized或者Lock代码块的加锁和释放锁的操作。所以静态内部类更加常用,比DCL更好,毕竟能不加锁最好就别加锁。至于"自旋锁"则是CAS机制,详细可以去了解JUC的相关知识。PS:后面也会出关于JUC的相关文章。
注册式单例
注册式单例就是把实例化对象放在一个Map集合中类的全限定类名作为key,该类的是实例化对象作为value
public class SingletonRegistryContainer {
private static final Map<String, Object> IOC = new ConcurrentHashMap<>();
private static Lock lock = new ReentrantLock();
private SingletonRegistryContainer() {
}
/**
* 借鉴spring中的单例注册
* @param className 全限定类名
* @return
*/
public static Object getBean(String className) {
Object obj;
if (!IOC.containsKey(className)) {
lock.lock();
try {
if (!IOC.containsKey(className)) {
//通过反射创建对象
obj = Class.forName(className).newInstance();
IOC.put(className, obj);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
return IOC.get(className);
}
}
简单的看一下Spring中单例对象的注册:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
protected static final Object NULL_OBJECT = new Object();
protected final Log logger = LogFactory.getLog(this.getClass());
//存放单例对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(64);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
private final Map<String, Object> earlySingletonObjects = new HashMap(16);
private final Set<String> registeredSingletons = new LinkedHashSet(64);
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
private final Set<String> inCreationCheckExclusions = Collections.newSetFromMap(new ConcurrentHashMap(16));
private Set<Exception> suppressedExceptions;
private boolean singletonsCurrentlyInDestruction = false;
private final Map<String, Object> disposableBeans = new LinkedHashMap();
private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap(16);
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap(64);
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap(64);
public DefaultSingletonBeanRegistry() {
}
//注册单例对象
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
Assert.notNull(beanName, "'beanName' must not be null");
synchronized(this.singletonObjects) {
Object oldObject = this.singletonObjects.get(beanName);
if (oldObject != null) {
throw new IllegalStateException("Could not register object [" + singletonObject + "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
} else {
//不存在,添加
this.addSingleton(beanName, singletonObject);
}
}
}
//添加单例对象到map中
protected void addSingleton(String beanName, Object singletonObject) {
synchronized(this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject != null ? singletonObject : NULL_OBJECT);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
//篇幅长,其他省略
...
}
枚举式单例
枚举式单例这就厉害,枚举类天生就是单例的。相信看过<Effetvice Java>
这本书中的"用私有构造器或者枚举类型强化Singleton"就说到使用枚举单例来强化单例(编写一个仅含有单个元素的枚举类型),因为就算是私有化构造方法,但是还是可以通过"反射"和"反序列化"就可以破解单例(这个下一个小节介绍,以及其解决方法),枚举类型却能够防止"反射"和"反序列化"的破坏。下面看一下代码实现:
package com.msr.study.patterns.creational.singleton;
import java.io.Serializable;
public enum SingletonRegistryEnum implements Serializable {
INSTANCE;
//反射和反序列化破坏时会用到
private Object data;
public static SingletonRegistryEnum getInstance() {
return INSTANCE;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
//测试
package com.msr.study.patterns.creational.singleton;
public class SingletonTest {
public static void main(String[] args) throws Exception {
SingletonRegistryEnum instance1 = SingletonRegistryEnum.getInstance();
SingletonRegistryEnum instance2 = SingletonRegistryEnum.getInstance();
System.out.println(instance1==instance2); //true
}
}
枚举类的源码也就这么简单,很难看得出来为什么枚举是单例的。但是我们可以通过反编译其class文件去一探究竟,下面使用到jad反编译工具。下载好jad之后,通过命令行执行。具体其他的用法可以百度
jad E:\java\...\SingletonRegistryEnum.class(class文件绝对路径)
执行后就会在就会在jad所在的目录生成 SingletonRegistryEnum.jad
,通过文本编辑器打开它
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: SingletonRegistryEnum.java
package com.msr.study.patterns.creational.singleton;
import java.io.Serializable;
public final class SingletonRegistryEnum extends Enum implements Serializable{
public static SingletonRegistryEnum[] values(){
return (SingletonRegistryEnum[])$VALUES.clone();
}
public static SingletonRegistryEnum valueOf(String name){
return (SingletonRegistryEnum)Enum.valueOf(com/msr/study/patterns/creational/singleton/SingletonRegistryEnum, name);
}
//没有空参的构造方法
//私有化的构造方法,两个参数---下一小节的反射破坏枚举时就会凸显出来了
private SingletonRegistryEnum(String s, int i) {
super(s, i);
}
public static SingletonRegistryEnum getInstance() {
return INSTANCE;
}
public Object getData(){
return data;
}
public void setData(Object data){
this.data = data;
}
public static final SingletonRegistryEnum INSTANCE;
private Object data;
private static final SingletonRegistryEnum $VALUES[];
static {
//静态代码块,近初始化一次,有且仅有一份---天生单例
INSTANCE = new SingletonRegistryEnum("INSTANCE", 0);
$VALUES = (new SingletonRegistryEnum[] {
INSTANCE
});
}
}
重头戏:单例对象的暴力破解(反射和反序列化)
上面的内容讲了那么多,又是否真的是真正可以做到单例呢?答案是不一定哟~
总所周知jdk的反射是十分强大,毕竟有人说反射是框架设计的灵魂,可见其功能的强大。它可以在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。使用过反射的都知道,就算是private
修饰,一样是可以通过反射去获取,setAccessible()
送private
上天。
把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列化。一般用途:把对象的字节序列化通过文件流永久地保存在磁盘中;在网络在传输对象的字节序列,也就是为什么在用json工具或者在web项目运行的时候,可能会出现Serializable的一些异常,有可能就是实体类没有继承Serializable接口。
下面编写一个基于序列化和反序列化去克隆对象的工具类。并且是深度克隆,在原型模式也可以使用这个工具类,不用去重写clone()
就可以达到深度克隆。而这个工具类的序列化和反序列化是基于内存,垃圾回收时就会清理,基于内存速度快。
package com.msr.study.patterns.creational.singleton;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.io.*;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ObjectCloneUtil {
public static <T extends Serializable> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
//垃圾回收时就会清理
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
// 说明:调用 ByteArrayInputStream 或 ByteArrayOutputStream 对象的 close 方法没有任何意义
// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
// 网上很多的反序列化的的例子用的是文件流:把对象序列化到磁盘的文件上,在读取文件反序列化生成对象
}
}
现在开始尝试用放射和反序列化破坏上面所说的单例模式的实现
public class BreakTest {
public static void main(String[] args) {
try {
System.out.println("==========破坏饿汉式==========");
//通过提供的接口去获取对象
SingletonHunger instance = SingletonHunger.getInstance();
//反射破坏
Class<?> aClass = Class.forName(SingletonHunger.class.getName());
Constructor<?> constructor = aClass.getDeclaredConstructor();
constructor.setAccessible(true);
//反射获取
SingletonHunger reflectObj = (SingletonHunger) constructor.newInstance();
System.out.println(reflectObj == instance); //false
//反序列化破坏
SingletonHunger clone = ObjectCloneUtil.clone(instance);
System.out.println(clone == instance); //false
System.out.println("==========破坏DCL==========");
//通过提供的接口去获取对象
SingletonLazy instance2 = SingletonLazy.getInstanceByDCL("张三", "16");
//反射破坏
Class<?> aClass2 = Class.forName(SingletonLazy.class.getName());
Constructor<?> constructor2 = aClass2.getDeclaredConstructor();
constructor2.setAccessible(true);
SingletonLazy reflectObj2 = (SingletonLazy) constructor2.newInstance();
System.out.println(reflectObj2 == instance2);
//反序列化破坏
SingletonLazy clone2 = ObjectCloneUtil.clone(instance2);
System.out.println(clone2 == instance2);
System.out.println("==========破坏静态内部类==========");
SingletonHolder instance3 = SingletonHolder.getInstanceByHolder();
Class<?> aClass3 = Class.forName(SingletonHolder.class.getName());
Constructor<?> constructor3 = aClass3.getDeclaredConstructor();
constructor3.setAccessible(true);
SingletonHolder reflectObj3 = (SingletonHolder) constructor3.newInstance();
System.out.println(instance3 == reflectObj3); //false
SingletonHolder clone3 = ObjectCloneUtil.clone(instance3);
System.out.println(clone3 == instance3); //false
} catch (Exception e) {
e.printStackTrace();
}
}
}
在通过反射去获取枚举类型的构造函数的时候,会出现NoSuchMethodException异常,在上一小节中通过对枚举类型的反编译之后,会发现只有一个两个参数的私有的构造方法。没有无参的构造方法。去获取两个参数的构造方法的时候,虽然成功获取不过还是会报错Cannot reflectively create enum objects,很明显是"不能通过反射去创建枚举对象"。
System.out.println("==========破坏枚举类型单例==========");
SingletonRegistryEnum instance4 = SingletonRegistryEnum.getInstance();
Class<?> aClass4 = Class.forName(SingletonRegistryEnum.class.getName());
//会直接报错:
//java.lang.NoSuchMethodException:com.msr.study.patterns.creational.singleton.SingletonRegistryEnum.<init>()
Constructor<?> constructor4 = aClass4.getDeclaredConstructor();
constructor4.setAccessible(true);
//那就去获取两个参数的构造方法
Constructor<?> constructor5 = aClass4.getDeclaredConstructor(String.class,int.class);
constructor5.setAccessible(true);
//newInstance()报错:java.lang.IllegalArgumentException: Cannot reflectively create enum objects
SingletonRegistryEnum reflectObj4 =(SingletonRegistryEnum) constructor4.newInstance()
//反编译的片段
private SingletonRegistryEnum(String s, int i) {
super(s, i);
}
WTF???为什么呢?下面来通过去看一下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);
}
}
//这些判断条件是本地方法,是通过底层的JVM去判断的
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects"); //真相大白
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
所以功能强大的反射遇到枚举类型就不太好使了。那么反序列化呢,它又会不会破坏单例?下面测试一下:
SingletonRegistryEnum instance4 = SingletonRegistryEnum.getInstance();
SingletonRegistryEnum registryEnum = ObjectCloneUtil.clone(instance4);
System.out.println(instance4==registryEnum); //结果是:true
为什么这么神奇?其实可以看一下反序列化中的ObjectInputStream的readObject()方法的源码:
public final Object readObject()throws IOException, ClassNotFoundException{
if (enableOverride) {
return readObjectOverride();
}
...
try {
//关键方法
Object obj = readObject0(false);
...
return obj;
} finally {
...
}
}
private Object readObject0(boolean unshared) throws IOException {
...
//switch语句
case TC_ENUM:
//关键readEnum()方法
return checkResolve(readEnum(unshared));
...
}
private Enum<?> readEnum(boolean unshared) throws IOException {
if (bin.readByte() != TC_ENUM) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
if (!desc.isEnum()) {
throw new InvalidClassException("non-enum class: " + desc);
}
int enumHandle = handles.assign(unshared ? unsharedMarker : null);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(enumHandle, resolveEx);
}
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}
handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}
可以发现枚举类型其实通过类名和 Class 对象类找到一个唯一的枚举对象。因此枚举对象不可能被类加载器加载多次。
解决方法:对于反序列化的解决方法就是在类中添加readResolve()方法。例如在静态内部类添加了之后,再去尝试使用反序列化破坏单例就不好使了。
private Object readResolve(){
return SingletonHolder.getInstanceByHolder();
}
原因是ObjectInputStream中的readObject()方法--->readObject0()方法--->readOrdinaryObject()方法
private Object readOrdinaryObject(boolean unshared)throws IOException{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
//调用了 ObjectStreamClass 的 isInstantiable()方法
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
...
return obj;
}
//代码很简单
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
isInstantiable()方法的代码很简单,就是判断一下构造方法是否为空,构造方法不为空就返回true。那就是意味着只要有无参构造方法就会实例化,很明显编写的单例类是含有一个无参的构造方法,最后`obj = desc.isInstantiable() ? desc.newInstance() : null;三目运算符还是创建了一个新对象,我们还需要再往下看readOrdinaryObject()方法中的代码。
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
//obj被创建,不为空
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
...
//if判断,调用hasReadResolveMethod()
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
再进一步又调用了hasReadResolveMethod()方法。逻辑又是非常简单,就是判断一下readResolveMethod是否为空。那么readResolveMethod 是在哪里赋值的呢?通过全局查找找到了赋值代码在私有方法ObjectStreamClass()方法中给 readResolveMethod 进行赋值。
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
其实就是通过反射区找到一个无参的readResolve()方法,并且保存下来。所以再回到ObjectInputStream 的 readOrdinaryObject()方法继续往下看,如果存在readResolve()方法就会调用 invokeReadResolve()方法,然后返回readResolveMethod.invoke(obj, (Object[]) null);
的结果。
Object invokeReadResolve(Object obj) throws IOException, UnsupportedOperationException{
requireInitialized();
if (readResolveMethod != null) {
try {
//调用readResolveMethod
return readResolveMethod.invoke(obj, (Object[]) null);
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof ObjectStreamException) {
throw (ObjectStreamException) th;
} else {
throwMiscException(th);
throw new InternalError(th); // never reached
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
其实在这程中还是会有一个新的对象产生,只不过是没有被返回,返回的是前面创建的对象,这样就会产生垃圾对象了。那如果,创建对象的动作发生频率增大,就意味着内存分配开销也就随之增大。
其实还有一种单例:线程内单例(ThreadLocal),那只是针对单个线程内,这里就不展开讲了。
总结
枚举类能够同时防止反射和反序列化的破坏,其他的方法只能通过添加Resolve()方法,至于反射就不太好解决。其实可以在开发的时候明确单例类不能通过反射去创建对象实例,并且在私有的构造方法内做一点保护措施。
if (lazy==null){
throw new RuntimeException("不能反射创建对象");
}else {
throw new RuntimeException("对象已创建");
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。