设计模式系列-你真的了解单例模式吗??
单例模式(Singleton Pattern)的定义相信大家都清楚,就是确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。接下来我们看看常见的单例模式及其优缺点。
饿汉式单例模式
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance() {
return instance;
}
}
饿汉式单例模式在类加载时就初始化,并且创建实例对象,它是线程安全的。饿汉式单例模式适用单例对象较少的系统,该写法可以保证线程安全、执行效率高。但是缺点也很明显,如果系统中存在大量单例对象,那么在系统初始化过程就创建了大量的对象实例,不管是否用到了对象实例都占用着空间,浪费内存。那么有没有更优的写法呢?
懒汉式单例模式
public class LazySimpleSingleton {
private static LazySimpleSingleton instance;
private LazySimpleSingleton() {};
public static LazySimpleSingleton getInstance() {
if(null == instance) {
instance = new LazySimpleSingleton();
}
return instance;
}
}
懒汉式单例模式解决了饿汉式单例模式类加载就创建实例的问题,只在使用时才创建实例对象。但这样写又带来一个新的问题,在多线程环境下存在线程安全问题。那么我们怎么解决懒汉式单例模式的线程安全问题呢?对多线程有一定了解的你们可能想到了,在getInstance方法上加锁即synchronized关键字,使其变为同步方法。
public class LazySimpleSingleton {
private static LazySimpleSingleton instance;
private LazySimpleSingleton() {};
public static synchronized LazySimpleSingleton getInstance() {
if(null == instance) {
instance = new LazySimpleSingleton();
}
return instance;
}
}
这种写法确实解决了内存浪费和线程安全问题,但是当大量线程同时获取实例时,只有一个线程获得锁进入getInstance方法,其他线程都要进入阻塞状态等待获得锁,导致程序性能大幅下降。那么有没有一种更好的方式既能解决线程安全问题又能兼顾性能呢?双重校验锁它来了。
public class LazyDoubleCheckSingleton {
private static LazyDoubleCheckSingleton instance;
private LazyDoubleCheckSingleton(){};
public static LazyDoubleCheckSingleton getInstance() {
if(null == instance) {
synchronized (LazyDoubleCheckSingleton.class) {
if(null == instance) {
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
}
双重校验锁解决了性能问题,但是终归synchronized关键字上要加锁,对于程序性能还是有一定的影响,那有没有更好的方法呢?我们可以利用类加载机制在使用时才加载实例,还可以避开线程安全问题。我们看一下代码:
public class LazyInnerSingleton {
private LazyInnerSingleton(){}
//final防止被重写、重载
public static final LazyInnerSingleton getInstance() {
return InstanceHolder.INSTANCE;
}
//静态内部类默认不加载,调用之前才会初始化,利用类加载机制巧妙避开了线程安全问题
private static class InstanceHolder {
private static final LazyInnerSingleton INSTANCE = new LazyInnerSingleton();
}
}
反射破坏单例
上面的写法就万无一失了吗?先看一段儿代码。
public class ReflectTest {
public static void main(String[] args) {
try {
Object instance1 = LazyInnerSingleton.getInstance();
Class<?> clazz = LazyInnerSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true); //强制访问
Object instance2 = constructor.newInstance();
//运行结果为:false
System.out.println(instance1 == instance2);
}catch (Exception e) {
e.printStackTrace();
}
}
}
我们可以看到,通过反射已经破坏了单例模式,那么我们怎么解决这个问题呢?是不是在私有构造方法里做一些限制,如果多次创建的话抛出异常就可以了。我们对LazyInnerSingleton类再做一下改动。
public class LazyInnerSingleton {
private LazyInnerSingleton(){
if(InstanceHolder.INSTANCE != null) {
throw new RuntimeException("不允许重复创建实例");
}
}
//final防止被重写、重载
public static final LazyInnerSingleton getInstance() {
return InstanceHolder.INSTANCE;
}
//静态内部类默认不加载,调用之前才会初始化,利用类加载机制巧妙避开了线程安全问题
private static class InstanceHolder {
private static final LazyInnerSingleton INSTANCE = new LazyInnerSingleton();
}
}
序列化破坏单例
一个java对象创建好后,有时候需要将对象序列化后写入磁盘,使用时再从磁盘读取数据进行反序列化创建对象实例,反序列化创建的对象会重新分配内存空间,也就是会新建一个对象实例。如果一个支持序列化的对象是单例模式,很明显就违背了单例模式的初衷,我们先来看一段序列化破坏单例的代码。
public class SerializableSingleton implements Serializable {
private static final SerializableSingleton instance = new SerializableSingleton();
private SerializableSingleton(){}
public static SerializableSingleton getInstance() {
return instance;
}
}
public class SerializableTest {
public static void main(String[] args) {
try {
SerializableSingleton instance = SerializableSingleton.getInstance();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("serializable.obj"));
objectOutputStream.writeObject(instance);
objectOutputStream.flush();
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("serializable.obj"));
SerializableSingleton serializableInstance = (SerializableSingleton) objectInputStream.readObject();
objectInputStream.close();
System.out.println(instance == serializableInstance);//结果:false
}catch (Exception e) {
e.printStackTrace();
}
}
}
现在我们的单例又被序列化破坏掉了,解决这个问题的方法也很简单,只要在单例累添加一个方法readResolve(),如下
public class SerializableSingleton implements Serializable {
private static final SerializableSingleton instance = new SerializableSingleton();
private SerializableSingleton(){}
public static SerializableSingleton getInstance() {
return instance;
}
public Object readResolve() {
return instance;
}
}
加上方法之后再次执行测试方法输出结果就是true了。为什么加了readResolve()方法就不会被序列化破坏了呢?我们进入ObjectInputStream的readObject()方法找一下原因。
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
-->进入readObject0方法,往下找到TC_OBJECT条件下checkResolve(readOrdinaryObject(unshared))
private Object readObject0(boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}
byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
totalObjectRefs++;
try {
switch (tc) {
case TC_NULL:
return readNull();
case TC_REFERENCE:
return readHandle(unshared);
case TC_CLASS:
return readClass(unshared);
case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
return readClassDesc(unshared);
case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));
case TC_ARRAY:
return checkResolve(readArray(unshared));
case TC_ENUM:
return checkResolve(readEnum(unshared));
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}
case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
-->进入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 = 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);
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;
}
为了清晰一些我们另起一个代码窗口,好接着上面的继续看,readOrdinaryObject方法中有一个isInstantiable()方法,就是判断目标类是否有无参构造方法。
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
我们再回到readOrdinaryObject方法继续看,有一段代码desc.hasReadResolveMethod(),readResolveMethod不为空就返回true
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
readResolveMethod是在哪里初始化的呢?通过全局搜索,我们找到下面一段代码
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
就是通过反射找到无参的readResolve方法并且保存下来。我们继续回到readOrdinaryObject方法,看到如果存在无参readResolve方法,则执行下面的方法invokeReadResolve,该方法通过反射调用了单例类的readResolve方法。
Object invokeReadResolve(Object obj)
throws IOException, UnsupportedOperationException
{
requireInitialized();
if (readResolveMethod != null) {
try {
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();
}
}
好。通过对jdk源码分析我们弄懂了readResolve()方法是怎么解决序列化破坏单例的问题,但是实际上对象创建了两次,只不过新创建的对象没有被返回,而是返回了单例类的readResolve()方法的返回对象。那么这种解决办法也同时存在着多余的内存开销问题,难道就没有办法彻底解决这个问题吗?
-
注册式单例模式 -
枚举式单例模式 -
容器式单例模式
1. 枚举式单例模式
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
public class EnumTest {
public static void main(String[] args) {
try {
EnumSingleton instance = EnumSingleton.getInstance();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("enum.obj"));
objectOutputStream.writeObject(instance);
objectOutputStream.flush();
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("enum.obj"));
EnumSingleton serializableInstance = (EnumSingleton) objectInputStream.readObject();
objectInputStream.close();
System.out.println(instance == serializableInstance);//运行结果:true
}catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果为true,为什么枚举式单例不会被序列化破坏呢?我们再找到上面ObjectInputStream中的readObject0方法,其中有一段代码
case TC_ENUM:
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和类名找到唯一枚举对象,因此不存在被类加载器加载多次。那么枚举单例会不会被反射破坏呢?我们找到java.lang.Enum类:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
我们写一个测试类尝试用反射破坏枚举单例
public static void main(String[] args) {
try {
Constructor constructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
EnumSingleton instance = (EnumSingleton) constructor.newInstance("老船长", 1);
}catch (Exception e) {
e.printStackTrace();
}
}
运行结果:Cannot reflectively create enum objects,结果很明显。
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at EnumReflectTest.main(EnumReflectTest.java:15)
Process finished with exit code 0
我们进入Constructor的newInstance方法找一下原因,如果修饰符是Modifier.ENUM就会抛出异常。
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);
}
}
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;
}
容器式单例
容器式单例就是提供一个全局Map来存储实例,很简单就不赘述了。
public class SingletonContainer {
private SingletonContainer() {}
private static Map<String, Object> ioc = new ConcurrentHashMap<>();
public static Object getInstance(String className) {
if(!ioc.containsKey(className)) {
try {
ioc.put(className, Class.forName(className).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
return ioc.get(className);
}
}
小结
今天的内容就到这里,我们总结了常见的几种单例模式写法,分析了如何破坏单例及怎么防止被破坏。
看到这里的帅哥美女们帮忙关注一下,期待和你一起学习一起成长。