这是我参与更文挑战的第19天,活动详情查看: 更文挑战
单例模式二
上节我们讲了一部分单例模式,接下来继续讲解静态内部类和枚举,以及源码中的单例模式。
静态内部类
package com.wangscaler.singleton;
/**
* @author wangscaler
* @date 2021.06.21 17:42
*/
public class Staticinnerclass {
public static void main(String[] args) {
Staticinnerclass staticinnerclass = Staticinnerclass.getInstance();
Staticinnerclass staticinnerclass1 = Staticinnerclass.getInstance();
System.out.println(staticinnerclass.hashCode());
System.out.println(staticinnerclass1.hashCode());
System.out.println(staticinnerclass == staticinnerclass1);
}
private Staticinnerclass() {
}
private static class StaticinnerInstance {
private static final Staticinnerclass INSTANCE = new Staticinnerclass();
}
public static Staticinnerclass getInstance() {
return StaticinnerInstance.INSTANCE;
}
}
复制代码
Staticinnerclass装载的时候,静态内部类StaticinnerInstance是不会装载的,只有代码调用 getInstance时,调用他,才会被初始化。这种模式既能保证线程安全又能保证懒加载。所以也推荐使用。
枚举
package com.wangscaler.singleton;
/**
* @author wangscaler
* @date 2021.06.21 17:59
*/
public class Enumeration {
public static void main(String[] args) {
EnumerationSingleton enumerationSingleton = EnumerationSingleton.INSTANCE;
EnumerationSingleton enumerationSingleton1 = EnumerationSingleton.INSTANCE;
System.out.println(enumerationSingleton.hashCode());
System.out.println(enumerationSingleton1.hashCode());
System.out.println(enumerationSingleton == enumerationSingleton1);
}
}
enum EnumerationSingleton {
INSTANCE;
}
复制代码
此种方式,不仅避免多线程同步问题,还防止反序列化重新创建新的对象,也是推荐使用的。
提到反序列化,那么反序列化时,会对单例模式造成什么影响呢?
反序列化对单例模式的影响
package com.wangscaler.singleton;
import java.io.*;
/**
* @author wangscaler
* @date 2021.06.18 11:18
*/
public class Hungryman {
public static void main(String[] args) throws IOException, ClassNotFoundException {
StatiConst statiConst1 = StatiConst.getInstance();
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("SingletonDeserialization"));
outputStream.writeObject(statiConst1);
File file = new File("SingletonDeserialization");
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
StatiConst statiConst = (StatiConst) inputStream.readObject();
System.out.println(statiConst.hashCode());
System.out.println(statiConst1.hashCode());
System.out.println(statiConst == statiConst1);
}
static class StatiConst implements Serializable{
//私有化之后,外部不可new
private StatiConst() {
}
private final static StatiConst instance = new StatiConst();
public static StatiConst getInstance() {
return instance;
}
}
}
复制代码
执行之后的结果为
1452126962
325040804
false
复制代码
从上述可以看出,将对象写入文件中,再从文件读出来,反序列化成对象之后,拿到的hash并不是之前的对象了。那么我们怎么让反序列化之后的对象还是之前的对象呢?只需要增加方法readResolve
,当JVM从内存中反序列化地”组装”一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 从而使得单例规则得到了保证。修改之后的代码
package com.wangscaler.singleton;
import java.io.*;
/**
* @author wangscaler
* @date 2021.06.18 11:18
*/
public class Hungryman {
public static void main(String[] args) throws IOException, ClassNotFoundException {
StatiConst statiConst1 = StatiConst.getInstance();
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("SingletonDeserialization"));
outputStream.writeObject(statiConst1);
File file = new File("SingletonDeserialization");
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
StatiConst statiConst = (StatiConst) inputStream.readObject();
System.out.println(statiConst.hashCode());
System.out.println(statiConst1.hashCode());
System.out.println(statiConst == statiConst1);
}
static class StatiConst implements Serializable{
//私有化之后,外部不可new
private StatiConst() {
}
private final static StatiConst instance = new StatiConst();
public static StatiConst getInstance() {
return instance;
}
private Object readResolve(){
return instance;
}
}
}
复制代码
执行结果
325040804
325040804
true
复制代码
可以看到,我们增加了这个方法之后,就达到了我们的预期效果,那么程序什么时候执行了这段代码?打开源码我们发现
/** if true, invoke readObjectOverride() instead of readObject() */
private final boolean enableOverride;
/**
* Read an object from the ObjectInputStream. The class of the object, the
* signature of the class, and the values of the non-transient and
* non-static fields of the class and all of its supertypes are read.
* Default deserializing for a class can be overridden using the writeObject
* and readObject methods. Objects referenced by this object are read
* transitively so that a complete equivalent graph of objects is
* reconstructed by readObject.
*
* <p>The root object is completely restored when all of its fields and the
* objects it references are completely restored. At this point the object
* validation callbacks are executed in order based on their registered
* priorities. The callbacks are registered by objects (in the readObject
* special methods) as they are individually restored.
*
* <p>Exceptions are thrown for problems with the InputStream and for
* classes that should not be deserialized. All exceptions are fatal to
* the InputStream and leave it in an indeterminate state; it is up to the
* caller to ignore or recover the stream state.
*
* @throws ClassNotFoundException Class of a serialized object cannot be
* found.
* @throws InvalidClassException Something is wrong with a class used by
* serialization.
* @throws StreamCorruptedException Control information in the
* stream is inconsistent.
* @throws OptionalDataException Primitive data was found in the
* stream instead of objects.
* @throws IOException Any of the usual Input/Output related exceptions.
*/
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();
}
}
}
/**
* Underlying readObject implementation.
*/
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);
}
}
/**
* Reads and returns "ordinary" (i.e., not a String, Class,
* ObjectStreamClass, array, or enum constant) object, or null if object's
* class is unresolvable (in which case a ClassNotFoundException will be
* associated with object's handle). Sets passHandle to object's assigned
* handle.
*/
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;
}
复制代码
在我们的程序中,调用inputStream.readObject()
之后,他会先判断是否有readObjectOverride(),如果有则取代readObject,在这里是没有的,所以程序 往下执行Object obj = readObject0(false);
执行完这句代码进入readObject0之后,因为我们是对象,所以会走case TC_OBJECT
里的readOrdinaryObject(unshared)
打开这个方法的源码我们发现有这样一句代码obj = desc.isInstantiable() ? desc.newInstance() : null;
先判断该对象是否可以实例化,,如果可以则进行实例化,之后
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
复制代码
通过desc.hasReadResolveMethod()
判断我们是否写了ReadResolve
方法,如果我们写了,则调用desc.invokeReadResolve(obj);
来调用我们写的ReadResolve
方法。打开invokeReadResolve的源码如下
/** class-defined readResolve method, or null if none */
private Method readResolveMethod;
/**
* Invokes the readResolve method of the represented serializable class and
* returns the result. Throws UnsupportedOperationException if this class
* descriptor is not associated with a class, or if the class is
* non-serializable or does not define 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();
}
}
复制代码
调用完我们写的方法之后,他会判断我们返回的对象,和他刚才生成的对象是否相同
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);
}
复制代码
如果不同,则将我们返回的对象,赋值给他之前生成的对象,从而是我们反序列化之后的对象还是之前的对象。那么为什么枚举类型的不要写这个方法就能拿到这个对象呢
/**
* Reads in and returns enum constant, or null if enum type is
* unresolvable. Sets passHandle to enum constant's assigned handle.
*/
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;
}
复制代码
我们可以在源码看到Enum.valueOf((Class)cl, name);
,直接根据名字将对象取出来,然后result = en;
赋值给之前的对象。
JDK中的单例模式
以JDK中的Runtime为例
public class Runtime {
private static Runtime getRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
}
复制代码
可以看到我们Runtime的源码就是使用的单例模式中的饿汉式。
public class Desktop {
/**
* Represents an action type. Each platform supports a different
* set of actions. You may use the {@link Desktop#isSupported}
* method to determine if the given action is supported by the
* current platform.
* @see java.awt.Desktop#isSupported(java.awt.Desktop.Action)
* @since 1.6
*/
public static enum Action {
/**
* Represents an "open" action.
* @see Desktop#open(java.io.File)
*/
OPEN,
/**
* Represents an "edit" action.
* @see Desktop#edit(java.io.File)
*/
EDIT,
/**
* Represents a "print" action.
* @see Desktop#print(java.io.File)
*/
PRINT,
/**
* Represents a "mail" action.
* @see Desktop#mail()
* @see Desktop#mail(java.net.URI)
*/
MAIL,
/**
* Represents a "browse" action.
* @see Desktop#browse(java.net.URI)
*/
BROWSE
};
private DesktopPeer peer;
/**
* Suppresses default constructor for noninstantiability.
*/
private Desktop() {
peer = Toolkit.getDefaultToolkit().createDesktopPeer(this);
}
/**
* Returns the <code>Desktop</code> instance of the current
* browser context. On some platforms the Desktop API may not be
* supported; use the {@link #isDesktopSupported} method to
* determine if the current desktop is supported.
* @return the Desktop instance of the current browser context
* @throws HeadlessException if {@link
* GraphicsEnvironment#isHeadless()} returns {@code true}
* @throws UnsupportedOperationException if this class is not
* supported on the current platform
* @see #isDesktopSupported()
* @see java.awt.GraphicsEnvironment#isHeadless
*/
public static synchronized Desktop getDesktop(){
if (GraphicsEnvironment.isHeadless()) throw new HeadlessException();
if (!Desktop.isDesktopSupported()) {
throw new UnsupportedOperationException("Desktop API is not " +
"supported on the current platform");
}
sun.awt.AppContext context = sun.awt.AppContext.getAppContext();
Desktop desktop = (Desktop)context.get(Desktop.class);
if (desktop == null) {
desktop = new Desktop();
context.put(Desktop.class, desktop);
}
return desktop;
}
}
复制代码
这里的Desktop就是使用的枚举类型的单例模式。
总结
综上所述,单例模式中,可以使用的为
-
饿汉式(单线程,会造成内存的浪费)
-
双重检验锁
-
静态内部类
-
枚举
使用情况:需要频繁创建销毁对象或者创建对象时耗时长、资源多的情况(如session工厂,工具类….),Runtime在开发过程中,可能经常触发,大大减少了资源的占用。
注意情况:要想使用单例模式,必须使用提供的get方法,而不能去new对象,比如Runtime中使用getRuntime方法获取Runtime对象。
好处:节省资源,提高性能。
缺点:如果长时间不被使用,就会被回收,丢失状态数据;如果使用该对象的程序太多,会导致溢出,也不适合频繁变化的对象