内容简介:它的原理是利用了类加载机制。执行这段代码会发现o1<>o2,这就破坏了单例。为什么呢?罪魁祸首就是如下代码,它是反射的newInstance()的底层实现。
一、静态内部类
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条指令:
- 根据类型分配一块内存区域
- 把第一条指令返回的内存地址压入操作数栈顶
- 调用类的构造函数
而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 InnerClassHelper.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,性能好;简单
缺点:无法避免被反射、反序列化破坏
二、枚举
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 这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。
三、ThreadLocal单例模式
public class Singleton {
private Singleton(){}
private static final ThreadLocal<Singleton> threadLocal =
new ThreadLocal<Singleton>(){
@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<LinkedBlockingDeque<String>> LOOKUP_KEY_HOLDER = new ThreadLocal() {
protected Object initialValue() {
return new LinkedBlockingDeque();
}
private DynamicDataSourceContextHolder() {
}
public static String getDataSourceLookupKey() {
LinkedBlockingDeque<String> 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<String> deque = (LinkedBlockingDeque)LOOKUP_KEY_HOLDER.get();
if (deque.isEmpty()) {
LOOKUP_KEY_HOLDER.remove();
} else {
deque.pollFirst();
}
}
};
}
PS:initialValue()一般是用来在使用时进行重写的,如果在没有set的时候就调用get,会调用initialValue方法初始化内容。
读者福利
分享免费学习资料
针对于Java程序员,我这边准备免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)
为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!
资料领取方式:加入Java技术交流群 963944895
, 点击加入群聊
,私信管理员即可免费领取
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 深入理解 FilterChainProxy【源码篇】
- 深入理解 WebSecurityConfigurerAdapter【源码篇】
- 深入koa2源码
- 深入理解channel:设计+源码
- 深入浅出Semaphore源码解析
- 深入剖析Vue源码 - 组件基础
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Alone Together
Sherry Turkle / Basic Books / 2011-1-11 / USD 28.95
Consider Facebookit’s human contact, only easier to engage with and easier to avoid. Developing technology promises closeness. Sometimes it delivers, but much of our modern life leaves us less connect......一起来看看 《Alone Together》 这本书的介绍吧!