博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java设计模式之单例模式(枚举、静态内部类、ThreadLocal)
阅读量:6102 次
发布时间:2019-06-20

本文共 8288 字,大约阅读时间需要 27 分钟。

hot3.png

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方法初始化内容。

转载于:https://my.oschina.net/u/3670641/blog/3049585

你可能感兴趣的文章
rsync 服务器配置过程
查看>>
预处理、const与sizeof相关面试题
查看>>
爬虫豆瓣top250项目-开发文档
查看>>
Elasticsearch增删改查
查看>>
oracle归档日志增长过快处理方法
查看>>
有趣的数学书籍
查看>>
teamviewer 卸载干净
查看>>
多线程设计模式
查看>>
解读自定义UICollectionViewLayout--感动了我自己
查看>>
SqlServer作业指定目标服务器
查看>>
UnrealEngine4.5 BluePrint初始化中遇到编译警告的解决办法
查看>>
User implements HttpSessionBindingListener
查看>>
抽象工厂方法
查看>>
ubuntu apt-get 安装 lnmp
查看>>
焊盘 往同一个方向增加 固定的长度方法 总结
查看>>
eclipse的maven、Scala环境搭建
查看>>
架构师之路(一)- 什么是软件架构
查看>>
jquery的冒泡和默认行为
查看>>
USACO 土地购买
查看>>
【原创】远景能源面试--一面
查看>>