“This is the 14th day of my Participation in the August More Text Challenge. Check out the details:August is more challenging”
Design Patterns – A Learning Journey of Factory Patterns
1. Singleton pattern definition
A Singleton Pattern is one that ensures that there is absolutely only one instance of a class in any case and provides a global point of access. The singleton pattern is the creation pattern. The singleton pattern is also widely used in real life, such as company CEO, department manager, etc. J2EE standard ServletContext, ServletContextConfig and so on, Spring framework ApplicationContext, database connection pool and so on are also singleton pattern.
Two, hungry han type singleton mode
The hungrier singleton pattern is initialized as soon as the class loads, and the singleton is created. It is absolutely thread-safe, it is instantiated before threads exist, and there can be no access security issues.
Next, let’s look at the standard code for hungrier singletons:
public class HungrySingleton {
/** * load mechanism: * static, then dynamic * first properties, then methods * from the top down */
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
private HungrySingleton(a) {}public static HungrySingleton getInstance(a) {
returnHUNGRY_SINGLETON; }}Copy the code
There is another way to write this, using the mechanism of static code blocks:
public class HungryStaticSingleton {
private static final HungryStaticSingleton HUNGRY_STATIC_SINGLETON;
static {
HUNGRY_STATIC_SINGLETON = new HungryStaticSingleton();
}
private HungryStaticSingleton(a) {}public static HungryStaticSingleton getInstance(a) {
returnHUNGRY_STATIC_SINGLETON; }}Copy the code
Both of these are very simple and easy to understand, and the Hungrier singleton pattern is suitable for situations where there are few singleton objects. This method can ensure absolute thread safety and high execution efficiency. The disadvantage is obvious, however, that all object classes are instantiated as soon as they are loaded. As a result, if a large number of singletons exist in the system, a large amount of memory is wasted when the system is initialized. That is, whether an object is used or not, it takes up space, wastes memory, and is likely to “stand in the manger”.
Three, lazy singleton mode
In order to solve the problem of memory waste caused by the hungry Singleton, the lazy singleton is written. The characteristic of the lazy singleton is that the singleton object is initialized when it is used. The following is a simple implementation of the lazy singleton.
public class LazySimpleSingleton {
private LazySimpleSingleton(a) {}private static LazySimpleSingleton instance;
public static LazySimpleSingleton getInstance(a) {
if (instance == null) {
instance = new LazySimpleSingleton();
}
returninstance; }}Copy the code
However, writing this way brings a new problem, if in a multi-threaded environment, there will be a thread safety problem (not unique). Simple simulation:
public class LazySimpleSingletonTest {
@Test
public void test(a) {
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
System.out.println("end");
}
class MyThread implements Runnable {
@Override
public void run(a) {
LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + ":"+ singleton); }}}Copy the code
The thread debugging mode of IDEA can be used to interfere with the execution process of multiple threads:
So, how can we optimize the code, how can we optimize the code, to make lazy singletons safe in a multi-threaded environment? Add the synchronized keyword to the getInstance() method to make it a thread synchronization method:
public class LazySimpleSingleton {
private LazySimpleSingleton(a) {}private static LazySimpleSingleton instance;
public static synchronized LazySimpleSingleton getInstance(a) {
if (instance == null) {
instance = new LazySimpleSingleton();
}
returninstance; }}Copy the code
The thread safety issue has been resolved. Synchronized, however, can cause a large number of threads to block, resulting in performance degradation. So, is there a better way to improve performance while maintaining thread safety? The answer is yes. Let’s look at the singleton pattern of double-checked locking (DCL) :
public class LazyDoubleCheckSingleton {
private LazyDoubleCheckSingleton(a) {}// Use volatile to disallow reordering of instructions
private static volatile LazyDoubleCheckSingleton instance;
public static LazyDoubleCheckSingleton getInstance(a) {
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
/ * * * 1. Allocate memory to this object * 2. Initialize the object * 3. Set lazy to the newly allocated memory address * 4. Initialize the access object */}}}returninstance; }}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 blocks. The blocking is inside the getInstance() method, as long as the logic is not too complex to be aware of by the caller.
DCL singleton pattern has been used in many framework source code, have seen the source students know ha!!
However, the use of the synchronized keyword is always locked, which has a certain performance impact. Is there really no better way, Mom? Of course, we can think in terms of class initialization, using static inner classes.
Static inner class singleton pattern
/** * This format takes into account the memory wastage of hunchy singletons and the performance problems of synchronized locks, perfectly masking both of them */
public class LazyInnerClassSingleton {
/** * When LazyInnerClassSingleton is used, the inner class LazyHolder is initialized by default. If not used, the inner class is not loaded */
private LazyInnerClassSingleton(a) {}/** * Each keyword is not redundant. Static is used to make singletons share space. Final guarantees that the method will not be overridden or overridden
public static final LazyInnerClassSingleton getInstance(a) {
return LazyHolder.LAZY_INNER_CLASS_SINGLETON;
}
// Not loaded by default
private static class LazyHolder {
private static final LazyInnerClassSingleton LAZY_INNER_CLASS_SINGLETON = newLazyInnerClassSingleton(); }}Copy the code
This method takes into account both the memory waste of hungrier singleton and the performance of synchronized singleton. Inner classes are initialized when the method is called, cleverly avoiding thread-safety issues.
Five, the destruction of the single routine
1. Reflection failure singleton
The static inner class singleton pattern is simple, but no one is perfect. Is this really perfect?
public class LazyInnerClassSingletonTest {
@Test
public void test(a) {
try {
// In a very boring situation, to destroy
Class clazz = LazyInnerClassSingleton.class;
// Get private constructors by reflection
Constructor constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
// Violent initialization
// The constructor is called twice, which is equivalent to new twice
Object o1 = constructor.newInstance();
Object o2 = constructor.newInstance();
System.out.println(o1 == o2);
} catch(Exception e) { e.printStackTrace(); }}}Copy the code
Answer: false
Obviously, you’ve created two different instances, so what do you do? Let’s do an optimization. Now let’s restrict the constructor to throw an exception if multiple iterations occur. Let’s look at the optimized code:
private LazyInnerClassSingleton(a) {
if(LazyHolder.LAZY_INNER_CLASS_SINGLETON ! =null) {
throw new RuntimeException("Not allowed to create multiple instances ~~~"); }}Copy the code
At this point, I think the history of the most cattle singleton pattern implementation way is completed.
2. Serialization breaks singletons
The static inner class singleton constructor constraint that limits reflection is broken by serialization.
Once a singleton is created, it is sometimes necessary to serialize the object and write it to disk, and then read the object from disk and deserialize it into an in-memory object the next time you use it. Deserialized objects are reallocated memory, that is, recreated. If the target object of the serialization is a singleton, it defeats the purpose of the singleton pattern and breaks the singleton. Let’s look at this code:
@Test
public void test2(a) {
try {
LazyInnerClassSingleton s1 = LazyInnerClassSingleton.getInstance();
FileOutputStream fos = new FileOutputStream("serializableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("serializableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
LazyInnerClassSingleton s2 = (LazyInnerClassSingleton) ois.readObject();
ois.close();
System.out.println("s1=" + s1);
System.out.println("s2=" + s2);
System.out.println(s1 == s2);
} catch(Exception e) { e.printStackTrace(); }}Copy the code
As can be seen from the run results, the deserialized object is inconsistent with the manually created object and is instantiated twice, which violates the design of the singleton pattern. How to ensure that the singleton pattern can be implemented in the serialization case? It’s as simple as adding the readResole() method. Look at the optimized code:
public class LazyInnerClassSingleton implements Serializable { private static final long serialVersionUID = -8484501356898167924L; private LazyInnerClassSingleton() { if (LazyHolder.LAZY_INNER_CLASS_SINGLETON ! = null) {throw new RuntimeException(" not allowed to create multiple instances ~~~"); } } public static final LazyInnerClassSingleton getInstance() { return LazyHolder.LAZY_INNER_CLASS_SINGLETON; } private static class LazyHolder { private static final LazyInnerClassSingleton LAZY_INNER_CLASS_SINGLETON = new LazyInnerClassSingleton(); } // Key code!! private Object readResolve() { return LazyHolder.LAZY_INNER_CLASS_SINGLETON; }}Copy the code
Finally solved, in order not to destroy the singleton, really painstakingly ah!!
To see why, you can read the readObject() method of ObjectInputStream.
Enumerative singleton pattern
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance(a) {
returnINSTANCE; }}Copy the code
Enumerative singletons, though elegantly written, have a few problems. Because it initializes all the objects in the class memory when the class loads, it is not different from the Hunhun-style, which is not suitable for creating a large number of singletons.
Seven,
The singleton pattern ensures that there is only one instance in memory, which reduces the memory overhead and avoids the duplication of resources.
Welcome to follow the wechat official account (MarkZoe) to learn from each other and communicate with each other.