Singleton mode can be written as long as it is a qualified development, but if we want to go deep into it, small singleton mode can involve many things, such as multi-threading security, lazy loading, performance and so on. And do you know how to write several singleton patterns? How to prevent reflection from destroying the singleton mode? Today, I will devote a chapter to the singleton model.
Concerning the concept of singleton mode, I am not explaining it here. I believe every little partner knows it well.
Let's get straight to the point:
Hungry man
public class Hungry { private Hungry() { } private final static Hungry hungry = new Hungry(); public static Hungry getInstance() { return hungry; } }
Hungry Chinese is the simplest way to write the singleton pattern, which guarantees the thread's safety. For a long time, I used the Hungry Chinese pattern to complete the singleton. Because it was simple enough, I later learned that Hungry Chinese would have a little problem. Look at the following code:
public class Hungry { private byte[] data1 = new byte[1024]; private byte[] data2 = new byte[1024]; private byte[] data3 = new byte[1024]; private byte[] data4 = new byte[1024]; private Hungry() { } private final static Hungry hungry = new Hungry(); public static Hungry getInstance() { return hungry; } }
In the Hungry class, I define four byte arrays. When the code runs, the four arrays are initialized and put into memory. If the getInstance method is not used for a long time, the object of the Hungry class is not needed. Isn't that a waste? I hope that only when the getInstance method is used will the singleton class be initialized and the data in the singleton class be loaded. So there's a second singleton model: the lazy man model.
Lazy Man Style (DCL)
public class LazyMan { private LazyMan() { } private static LazyMan lazyMan; public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } return lazyMan; } }
DCL lazy singleton guarantees thread security, and conforms to lazy loading. Only when it is used, it will initialize, and the call efficiency is relatively high. However, this writing method may have some problems in extreme cases. because
lazyMan = new LazyMan();
It's not an atomic operation. It goes through at least three steps:
- Allocated memory
- Execution Construction Method
- Pointing address
As a result of instruction rearrangement, thread A may execute lazyMan = new LazyMan(); when the third step is executed (not yet the second step), thread B comes in again, finds that lazyMan is not empty, returns to lazyMan directly, and then uses the returned lazyMan. Because thread A has not executed the second step, lazyMan is incomplete at this time, and may have some. Some unexpected mistakes, so there is a singleton model.
Volatile
This singleton pattern only adds a volatile keyword to the DCL singleton pattern above to avoid instruction rearrangement:
public class LazyMan { private LazyMan() { } private volatile static LazyMan lazyMan; public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } return lazyMan; } }
holder
public class Holder { private Holder() { } public static Holder getInstance() { return InnerClass.holder; } private static class InnerClass { private static final Holder holder = new Holder(); } }
This method is the first hungry Chinese version of the improved version, but also in the class definition of static variables object, and direct initialization, but moved to the static inner class, very clever. It not only guarantees thread security, but also satisfies lazy loading.
Evil Reflection
Evil reflection has come on the scene. Reflection is a more domineering thing. Ignoring the construction method of private decoration, it can be directly outside the new Instance, destroying the singleton model we have worked hard to write.
public static void main(String[] args) { try { LazyMan lazyMan1 = LazyMan.getInstance(); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan lazyMan2 = declaredConstructor.newInstance(); System.out.println(lazyMan1.hashCode()); System.out.println(lazyMan2.hashCode()); System.out.println(lazyMan1 == lazyMan2); } catch (Exception e) { e.printStackTrace(); } }
We print out the hashcode of lazyMan1, lazyMan2 and whether lazyMan1 is equal to lazyMan2 respectively. The results are obvious:
So, how to solve this problem?
public class LazyMan { private LazyMan() { synchronized (LazyMan.class) { if (lazyMan != null) { throw new RuntimeException("Don't try to break the singleton pattern with reflection"); } } } private volatile static LazyMan lazyMan; public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } return lazyMan; } }
In the private constructor, if lazyMan is not empty, lazyMan has been created. If the getInstance method is called normally, this will not happen, so throw an exception directly:
But there are still some problems with this way of writing:
Above, we call the getInstance method to create LazyMan objects, so the second time we use reflection to create objects, the judgment in the private constructor works, and reflection destroys the singleton mode. But if the destroyer simply does not call the getInstance method first and creates objects directly with reflection, our judgment will not work:
public static void main(String[] args) { try { Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan lazyMan1 = declaredConstructor.newInstance(); LazyMan lazyMan2 = declaredConstructor.newInstance(); System.out.println(lazyMan1.hashCode()); System.out.println(lazyMan2.hashCode()); } catch (Exception e) { e.printStackTrace(); } }
So how to prevent this kind of reflection damage?
public class LazyMan { private static boolean flag = false; private LazyMan() { synchronized (LazyMan.class) { if (flag == false) { flag = true; } else { throw new RuntimeException("Don't try to break the singleton pattern with reflection"); } } } private volatile static LazyMan lazyMan; public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } return lazyMan; } }
Here, I define a boolean variable flag, the initial value is false, a judgment is made in the private constructor, if flag=false, flag is changed to true, but if flag equals true, it is a problem, because the normal call will not run to the private constructor for the second time, so throw an exception:
It looks good, but it still doesn't completely prevent reflection from destroying the singleton mode, because reflection can be used to modify flag values.
It doesn't seem that there's a good way to avoid reflective damage to the singleton mode, so it's our turn to enumerate.
enumeration
public enum EnumSingleton { instance; public EnumSingleton getInstance(){ return instance; } }
Enumeration is currently the most recommended way to write a singleton pattern, because it is simple enough to not develop itself to ensure thread security, but also to effectively prevent reflection from destroying our singleton pattern, we can see the source code of new Instance:
The key is the circled part in the red box, and if you enumerate the new Instance, you throw the exception directly.
Okay, that's the end of this chapter. Next time someone asks you about your singleton pattern, don't be afraid anymore.