java枚举 内部类_java设计模式之单例模式(枚举、静态内部类、ThreadLocal)

1、静态内部类

public class InnerClassSingleton implements Serializable {

//无参构造函数

private InnerClassSingleton(){};

public static final InnerClassSingleton getInstance(){

return InnerClassHelper.INSTANCE;

}

//内部类

private static class InnerClassHelper{

private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();

}

}

它的原理是利用了类加载机制。

1.1、但是它可以被反射破坏

Class clazz = InnerClassSingleton.class;

Constructor c = clazz.getDeclaredConstructor(null);

c.setAccessible(true);

Object o1 = c.newInstance();

Object o2 = InnerClassSingleton.getInstance();

执行这段代码会发现o1<>o2,这就破坏了单例。

为什么呢?罪魁祸首就是如下代码,它是反射的newInstance()的底层实现。

UnsafeFieldAccessorImpl.unsafe.allocateInstance(class)

我们知道new创建对象时会被编译成3条指令:

1、根据类型分配一块内存区域

2、把第一条指令返回的内存地址压入操作数栈顶

3、调用类的构造函数

而Unsafe.allocateInstance()方法值做了第一步和第二步,即分配内存空间,返回内存地址,没有做第三步调用构造函数。所以Unsafe.allocateInstance()方法创建的对象都是只有初始值,没有默认值也没有构造函数设置的值,因为它完全没有使用new机制,绕过了构造函数直接操作内存创建了对象,而单例是通过私有化构造函数来保证的,这就使得单例失败。

1.2、还可以被反序列化破坏

InnerClassSingleton o1 = null;

InnerClassSingleton o2 = InnerClassSingleton.getInstance();

FileOutputStream fos = new FileOutputStream("InnerClassSingleton.obj");

ObjectOutputStream oos = new ObjectOutputStream(fos);

oos.writeObject(o2);

oos.flush();

oos.close();

FileInputStream fis = new FileInputStream("InnerClassSingleton.obj");

ObjectInputStream ois = new ObjectInputStream(fis);

o1 = (InnerClassSingleton) ois.readObject();

ois.close();

System.out.println(o1);

System.out.println(o2);

执行完这段代码我们又会发现o1<>o2,可见通过反序列化,成功破坏了单例,创建了2个对象。

那么如何避免这种情况发生呢?很简单,只要在代码中添加:

public class InnerClassSingleton implements Serializable {

....省略重复代码

private Object readResolve(){

return InnerClassHelper.INSTANCE;

}

}

这时候我们可以再执行一下上面反序列化的方法,会很神奇的发现o1==o2,那这是为什么呢?我们一起来看下ois.readObject()的源码:

private Object readObject0(boolean unshared) throws IOException {

...省略

case TC_OBJECT:

return checkResolve(readOrdinaryObject(unshared));

}

-------------------------------------------------------------------

private Object readOrdinaryObject(boolean unshared){

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 {

//重点!!!

//首先isInstantiable()判断是否可以初始化

//如果为true,则调用newInstance()方法创建对象,这时创建的对象是不走构造函数的,是一个新的对象

obj = desc.isInstantiable() ? desc.newInstance() : null;

} catch (Exception ex) {

throw (IOException) new InvalidClassException(

desc.forClass().getName(),

"unable to create instance").initCause(ex);

}

passHandle = handles.assign(unshared ? unsharedMarker : obj);

ClassNotFoundException resolveEx = desc.getResolveException();

if (resolveEx != null) {

handles.markException(passHandle, resolveEx);

}

if (desc.isExternalizable()) {

readExternalData((Externalizable) obj, desc);

} else {

readSerialData(obj, desc);

}

handles.finish(passHandle);

//重点!!!

//hasReadResolveMethod()会去判断,我们的InnerClassSingleton对象中是否有readResolve()方法

if (obj != null &&

handles.lookupException(passHandle) == null &&

desc.hasReadResolveMethod())

{

//如果为true,则执行readResolve()方法,而我们在自己的readResolve()方法中 直接retrun INSTANCE,所以还是返回的同一个对象,保证了单例

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;

}

最后总结一下静态内部类写法:

优点:不用synchronized,性能好;简单

缺点:无法避免被反射、反序列化破坏

2、枚举

public enum EnumSingleton {

INSTANCE;

private Object data;

public Object getData() {

return data;

}

public void setData(Object data) {

this.data = data;

}

public static EnumSingleton getInstance() {

return INSTANCE;

}

}

反编译这段代码,得到:

static

{

INSTANCE = new EnumSingleton("INSTANCE",0);

$VALUE = (new EnumSingleton[] {

INSTANCE

});

}

显然这是一种饿汉式的写法,用static代码块来保证单例(在类加载的时候就初始化了)。

2.1、可以避免被反射破坏

//反射

Class clazz = EnumSingleton.class;

//拿到构造函数

Constructor c = clazz.getDeclaredConstructor(String.class, int.class);

c.setAccessible(true);

EnumSingleton instance1 = (EnumSingleton)c.newInstance("smart", 111);

-----------------------------------------------------------------------------------------

public T newInstance(Object ... initargs){

if ((clazz.getModifiers() & Modifier.ENUM) != 0)

throw new IllegalArgumentException("Cannot reflectively create enum objects");

}

可以看到,在newInstance()方法中,做了类型判断,如果是枚举类型,直接抛出异常。也就是说从jdk层面保证了枚举不能被反射。

2.2、可以避免被反序列化破坏

Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的 valueOf() 方法来根据名字查找枚举对象。

...省略

EnumSingleton o1 = (EnumSingleton) ois.readObject();

-----------------------------------------------------------------------------------

private Object readObject0(boolean unshared) throws IOException {

...省略

case TC_ENUM:

return checkResolve(readEnum(unshared));

}

-------------------------------------------------------------------

private Object readEnum(boolean unshared){

...省略

String name = readString(false);

Enum> result = null;

Class> cl = desc.forClass();

if (cl != null) {

try {

@SuppressWarnings("unchecked")

//重点!!!

//通过valueOf方法获取Enum,参数为class和name

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);

}

}

}

所以序列化的时候只将 INSTANCE 这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。

3、ThreadLocal单例模式

public class Singleton {

private Singleton(){}

private static final ThreadLocal threadLocal =

new ThreadLocal(){

@Override

protected Singleton initialValue(){

return new Singleton();

}

};

public static Singleton getInstance(){

return threadLocal.get();

}

}

这种写法利用了ThreadLocal的特性,可以保证局部单例,即在各自的线程中是单例的,但是线程与线程之间不保证单例。

应用场景(在Spring的第三方包baomidou的多数据源中,有用到这种写法):

package com.baomidou.dynamic.datasource.toolkit;

import java.util.concurrent.LinkedBlockingDeque;

public final class DynamicDataSourceContextHolder {

//重点!!!

private static final ThreadLocal> LOOKUP_KEY_HOLDER = new ThreadLocal() {

protected Object initialValue() {

return new LinkedBlockingDeque();

}

private DynamicDataSourceContextHolder() {

}

public static String getDataSourceLookupKey() {

LinkedBlockingDeque deque = (LinkedBlockingDeque)LOOKUP_KEY_HOLDER.get();

return deque.isEmpty() ? null : (String)deque.getFirst();

}

public static void setDataSourceLookupKey(String dataSourceLookupKey) {

((LinkedBlockingDeque)LOOKUP_KEY_HOLDER.get()).addFirst(dataSourceLookupKey);

}

public static void clearDataSourceLookupKey() {

LinkedBlockingDeque deque = (LinkedBlockingDeque)LOOKUP_KEY_HOLDER.get();

if (deque.isEmpty()) {

LOOKUP_KEY_HOLDER.remove();

} else {

deque.pollFirst();

}

}

};

}

PS:initialValue()一般是用来在使用时进行重写的,如果在没有set的时候就调用get,会调用initialValue方法初始化内容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的枚举类型可以与ThreadLocal一起使用。ThreadLocal是一种线程局部变量,它为每个线程提供了一个独立的变量副本。通过将ThreadLocal枚举类型结合使用,可以确保每个线程都有自己的枚举实例。 在Java中,可以使用ThreadLocal来创建线程局部的枚举实例。通过在ThreadLocal中存储枚举实例,可以确保每个线程都可以独立地访问和修改自己的枚举实例,而不会影响其他线程的实例。 以下是一个示例代码,演示了如何在枚举中使用ThreadLocal: ```java public enum MyEnum { INSTANCE; private ThreadLocal<MyObject> threadLocal = new ThreadLocal<MyObject>() { @Override protected MyObject initialValue() { return new MyObject(); } }; public MyObject getThreadLocalObject() { return threadLocal.get(); } } ``` 在上面的示例中,MyEnum是一个枚举类型,它包含一个ThreadLocal变量threadLocal。每个线程可以通过调用getThreadLocalObject()方法来获取自己的MyObject实例。 使用枚举ThreadLocal的好处是,每个线程都可以独立地访问和修改自己的实例,而不会干扰其他线程的实例。这在多线程环境下非常有用,可以确保线程安全性和数据隔离。 引用: \[1\] Java规范中关于枚举的定义和序列化的说明 \[2\] 一个使用ThreadLocal单例模式示例 \[3\] 一个测试类,演示了枚举的序列化破坏问题 #### 引用[.reference_title] - *1* [java设计模式单例模式枚举静态内部类ThreadLocal)](https://blog.csdn.net/weixin_33804990/article/details/92398414)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [单例模式(三)最佳实践枚举单例与ThreadLocal的单例](https://blog.csdn.net/qq_32048567/article/details/125922542)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值