The singleton pattern
Singleton schema definition and application scenario
The Singleton Pattern ensures that a class has absolutely one instance in any case and provides a global access point. The singleton pattern is the creation pattern. Singleton pattern is also widely used in display life, ServletContext, Spring framework applications, database connection pool, etc
Hunchman singleton pattern
The Hanchian singleton pattern is initialized as soon as the class is loaded and the singleton object is created. It is absolutely thread-safe, instantiated before the thread even exists, and there can be no access security issues.
Let’s look at the standard code for hungry singletons:
/** * All classes are initialized at class load time, resulting in a large amount of memory waste. This object may not use */
public class HungrySingleton {
/** * The HungrySingleton object is created when the JVM loads, and instances can also be generated using static code blocks */
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
/** * singleton must be thread privatized */
private HungrySingleton(a){}/** * returns the created entity class *@return* /
public static HungrySingleton getInstance(a){
returnHUNGRY_SINGLETON; }}Copy the code
Another way to write this is to use the mechanism of static code blocks:
```java
/** * All classes are initialized at class load time, resulting in a large amount of memory waste. This object may not use */
public class HungrySingleton {
/** * The HungrySingleton object is created when the JVM loads, and instances can also be generated using static code blocks */
private static final HungrySingleton HUNGRY_SINGLETON = null;
/** * singleton must be thread privatized */
private HungrySingleton(a){
HUNGRY_SINGLETON = new HungrySingleton();
}
/** * returns the created entity class *@return* /
public static HungrySingleton getInstance(a){
returnHUNGRY_SINGLETON; }}Copy the code
Both of these are very simple and easy to understand, and the Hunchman singleton pattern works well when there are fewer singletons. Writing in this way can ensure absolute thread safety, pointing efficiency is relatively high. However, its disadvantage is obvious, that is, all object classes are instantiated when loaded. As a result, if a large number of singletons exist in the system, the system initialization will result in a large amount of memory waste. In other words, objects take up space whether they are being used or not, wasting memory and potentially “squatting in the manger”. Is there a better way to write it?
Lazy singleton pattern
In order to solve the problem of memory waste caused by the lazy singleton, the lazy singleton pattern is characterized by the singleton object is not initialized until it is used. The following simple implementation of the lazy singleton pattern
public class LazySimpleSingleton {
private static LazySimpleSingleton lazy = null;
/** * privatize the constructor */
private LazySimpleSingleton(a){}
/** * Instantiate the LazySimpleSingleton object *@return* /
public static LazySimpleSingleton getInstance(a){
// Check if it is the first time to get the object, if it is the first time to return the new object, if it is not the first time return directly
if (lazy==null){
lazy = new LazySimpleSingleton();
}
returnlazy; }}Copy the code
But writing this raises a new issue, which is thread safety in a multithreaded environment. I’ll start by simulating the thread class ExectorThread:
public class ExectorThread implements Runnable{
@Override
public void run(a) {
LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+lazySimpleSingleton); }}Copy the code
The client test code is as follows:
public class LazySimpleSingletonTest{
public static void main(String[] args){
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
System.out.print("End"); }}Copy the code
Results:However, after many tests, different results will appear, as shown in the following figure:This means that the singleton pattern above has thread safety implications.
How do you solve this problem to make lazy singletons safe in multithreaded environments? Add the synchronized keyword to getInstance() to make this a thread-synchronized method:
public class LazySimpleSingleton {
private static LazySimpleSingleton lazy = null;
/** * privatize the constructor */
private LazySimpleSingleton(a){}
/** * Instantiate the LazySimpleSingleton object *@return* /
public synchronized static LazySimpleSingleton getInstance(a){
// Check if it is the first time to get the object, if it is the first time to return the new object, if it is not the first time return directly
if (lazy==null){
lazy = new LazySimpleSingleton();
}
returnlazy; }}Copy the code
At this point, we run the code
At this point, no matter how we run the code, the address will not change, and the thread-safety problem is solved. However, in the case of a large number of threads with synchronized locking, if the CPU allocation pressure increases, a large number of threads will be blocked, resulting in a significant decline in program performance. So, is there a better way to be thread-safe and improve application performance? The answer is yes. Let’s look at the singleton pattern for double-checked locks:
public class LazySimpleSingleton {
private static LazySimpleSingleton lazy = null;
/** * privatize the constructor */
private LazySimpleSingleton(a){}
/** * Instantiate the LazySimpleSingleton object *@return* /
public static LazySimpleSingleton getInstance(a){
// Check if it is the first time to get the object, if it is the first time to return the new object, if it is not the first time return directly
if (lazy==null) {synchronized(LazySimpleSingleton.class){
if (lazy==null){
lazy = newLazySimpleSingleton(); }}}returnlazy; }}Copy the code
When the first thread calls the getInstance() method, the second thread can also call it. When the first thread executes to synchronized, it locks, and the second thread becomes MONITOR and blocks. At this point, the blocking is not based on the entire LazySimpleSingleton class, but inside the getInstance() method, as long as the logic is not too complicated to be perceived by the caller.
However, synchronized keyword always need to lock, or there is a certain impact on the program performance. Is there really no better way? Sure, we can think of class initialization from the point of view of the following code, using static inner class:
public class LazySimpleSingleton {
private static LazySimpleSingleton lazy = null;
/** * privatize the constructor */
private LazySimpleSingleton(a){}
/** * Instantiate the LazySimpleSingleton object *@return* /
public static LazySimpleSingleton getInstance(a){
// Check if it is the first time to get the object, if it is the first time to return the new object, if it is not the first time return directly
return LazyHolder.LAZY;
}
private static class LazyHolder{
private static final LazySimpleSingleton LAZY = newLazySimpleSingleton(); }}Copy the code
This method takes into account both the memory waste problem of hunger-singleton and the performance problem of synchronized. The inner class must be initialized before the method is called, cleverly avoiding thread-safety issues. Because it's easy, we're not going to take you step by step. However, no one is perfect, and so is the singleton pattern.Copy the code
Reflection destruction singleton
The singleton constructor described above does nothing except add the private keyword. If we call its constructor using reflection and then the getInstance() method, there should be two different instances. Now take a look at the test code, using LazySimplessingleton as an example:
public class Test {
public static void main(String[] args) {
try {
// In a boring situation, destroyClass<? > clazz = LazySimpleSingleton.class;// Get the private constructor by reflection
Constructor c = clazz.getDeclaredConstructor(null);
// Force access
c.setAccessible(true);
// Violence initialization
Object o1 = c.newInstance();
// The constructor is called twice, which is equivalent to "new" twice
Object o2 = c.newInstance();
System.out.println(o1 == o2);
}catch(Exception e){ e.printStackTrace(); }}}Copy the code
Results:
Obviously, two different instances have been created, so what happens? Let’s do an optimization. Now, let’s put some restrictions in its constructor to throw an exception directly if multiple iterations occur. Take a look at the optimized code:
public class LazySimpleSingleton {
private static LazySimpleSingleton lazy = null;
/** * privatize the constructor */
private LazySimpleSingleton(a){
if(LazyHolder.LAZY! =null) {throw new RuntimeException("Creating multiple instances is not allowed"); }}/** * Instantiate the LazySimpleSingleton object *@return* /
public static LazySimpleSingleton getInstance(a){
// Check if it is the first time to get the object, if it is the first time to return the new object, if it is not the first time return directly
return LazyHolder.LAZY;
}
private static class LazyHolder{
private static final LazySimpleSingleton LAZY = new LazySimpleSingleton();
}
Copy the code
Results:At this point, the implementation of the singleton pattern, which is considered to be the greatest in history, is complete. However, the seemingly perfect singletons above can be broken.
Serialization breaks singletons
Once a singleton object is created, it is sometimes necessary to serialize the object and write it to disk, then read the object from disk and deserialize it to memory for next use. Deserialized objects are reallocated, or recreated. If the target object of serialization is a singleton, then the singleton pattern is broken. Let’s look at this code:
public class SerializableSingleton implements Serializable {
public final static SerializableSingleton INSTANCE = new SerializableSingleton();
private SerializableSingleton(a){}
public static SerializableSingleton getInstance(a){
returnINSTANCE; }}Copy the code
Write test code:
public class SerializableSingletonTest {
public static void main(String[] args) {
SerializableSingleton s1 = null;
SerializableSingleton s2 = SerializableSingleton.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("SerializableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SerializableSingleton) ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}catch(Exception e){ e.printStackTrace(); }}}Copy the code
As you can see from the run result, the deserialized object is still inconsistent with the manually created object. Instantiation twice violates the original intention of the design pattern. So how do we ensure that we can implement the singleton pattern even in serialization? It’s as simple as adding the readResolve() method. Take a look at the optimized code:
public class SerializableSingleton implements Serializable {
public final static SerializableSingleton INSTANCE = new SerializableSingleton();
private SerializableSingleton(a){}
public static SerializableSingleton getInstance(a){
return INSTANCE;
}
private Object readResolve(a){
returnINSTANCE; }}Copy the code
You must be thinking: what is the reason for this? Why do you write that? It looks amazing, but it’s also a little puzzling. Let’s take a look at the source implementation of the JDK to understand. We do the ObjectInputStream class’s readObject() method as follows:
public final Object readObject(a)
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(); }}}Copy the code
We see that the overridden readObject0() method is called in the readObject() method. Enter the readObject0() method with the following code:
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); }}Copy the code
It found a passage:
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
Copy the code
TC_OBJECT calls ObjectInputStream’s readOrdinaryObject() method.
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;
}
Copy the code
We find that the isInstantiable() method of ObjectStreamClass is called and the code for the isInstantiable() method is as follows:
boolean isInstantiable(a) {
requireInitialized();
return(cons ! =null);
}
Copy the code
The code above is simple enough to check if the constructor is null and return true if it is not. This means that as long as the parameterless constructor is instantiated.
At this point, the readResolve() method had not yet been found to prevent the singleton pattern from being broken. Go back to the readOrdinaryObject() method of ObjectInputStream and look at the hasReadResolveMethod() method after checking whether the no-argument constructor exists:
desc.hasReadResolveMethod())
Copy the code
HasReadResolveMethod () = hasReadResolveMethod();
boolean hasReadResolveMethod(a) {
requireInitialized();
return(readResolveMethod ! =null);
}
Copy the code
ReadResolveMethod is null and returns true if it is not. So where is readResolveMethod assigned? ObjectStreamClass() assigns readResolveMethod to the private ObjectStreamClass() method.
readResolveMethod = getInheritableMethod(
cl, "readResolve".null, Object.class);
Copy the code
The logic above simply finds a readResolve() method with no arguments by reflection and saves it. Now go back to ObjectInputStream’s readOrdinaryObject() method and continue, calling invokeReadResolve() if readResolve() exists, and look at the code:
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 newInternalError(ex); }}else {
throw newUnsupportedOperationException(); }}Copy the code
We can see that reflection calls the readResolveMethod method in the invokeReadResolve() method.
From JDK source code analysis, we can see that while adding an instance of the readResolve() method to return solves the problem of broken singletons, it actually instantiates twice, but the newly created object is not returned. If the act of creating objects happens more frequently, which means that the memory allocation overhead will also increase, is there really no way to solve the root of the problem? Let’s look at the registered singleton.
Registered singleton mode
The registered singleton mode, also known as the registered singleton mode, registers each instance in a certain place and obtains the instance with a unique identifier. There are two types of registered singleton pattern: enumeration singleton pattern and container singleton pattern.
Enumerated singleton pattern
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData(a){
return data;
}
public void setData(Object data){
this.data = data;
}
public static EnumSingleton getInstance(a){
returnINSTANCE; }}Copy the code
Testing:
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton instance1 = null;
EnumSingleton instance2 = EnumSingleton.getInstance();
instance2.setData(new Object());
FileOutputStream fos = null;
try {
fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
instance1 = (EnumSingleton) ois.readObject();
ois.close();
System.out.println(instance1.getData());
System.out.println(instance2.getData());
System.out.println(instance1.getData() == instance2.getData());
}catch(Exception e){ e.printStackTrace(); }}}Copy the code
No processing was done and we found that the results were as expected. So what is the mystery of the enumerated singleton pattern? Click on the following collection, and then there will be a single article.
Container singleton
In fact, enumeration singletons, although written elegantly, have some problems. Because it initializes all objects in class memory at class load time, this is no different from hanhan-style and is not suitable for scenarios where a large number of singleton objects are created. So, another way to write a registered singleton is to create a ContainerSingleton class:
public class ContainerSingleton {
private ContainerSingleton(a){}
private static Map<String , Object> ioc = new ConcurrentHashMap<>();
public static Object getBean(String className){
synchronized (ioc){
if(! ioc.containsKey(className)){ Object obj =null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className , obj);
}catch (Exception e){
e.printStackTrace();
}
return obj;
}else{
returnioc.get(className); }}}}Copy the code
The container singleton mode applies to scenarios where a large number of singleton objects are created, facilitating management. But it’s not thread-safe.