The singleton pattern is the easiest to understand, the easiest to use, and the most error-prone design pattern. It can be implemented in many ways, but not all of them are correct. Hungry mode, that is, urgent, immediately, so hungry mode is also called load now, the following code implementation is its most common writing.
public class SingletonObject {
private static final SingletonObject singletonObject = new SingletonObject();
private SingletonObject() {
}
public static SingletonObject getInstance() {
returnsingletonObject; }}Copy the code
The singleton implemented in this way is created at class load time and is thread-safe. The disadvantage is obvious: you can’t rely on parameters or configuration files to create objects. Lazy mode, that is, lazy, not in a hurry, so lazy mode is also called lazy loading, the following code is its implementation.
public class SingletonObject {
private static SingletonObject singletonObject;
private SingletonObject() {
}
public static SingletonObject getInstance() {
if (singletonObject == null) {
singletonObject = new SingletonObject();
}
returnsingletonObject; }}Copy the code
It can create objects based on parameters or configuration, which is common, but wrong, because the getInstance method has no synchronization control, which can lead to thread-unsafe situations in which multiple instances are created. In order to solve the non-thread-safe problem of lazy mode, the synchronous version implements the following script.
public class SingletonObject {
private static SingletonObject singletonObject;
private SingletonObject() {
}
synchronized public static SingletonObject getInstance() {
if (singletonObject == null) {
singletonObject = new SingletonObject();
}
returnsingletonObject; }}Copy the code
Although this implementation solves the non-thread-safe problem, it is thread-safe, but it is inefficient, because in the case of multi-thread concurrency, getInstance method needs to be synchronized, and only one thread can access execution at a time, and queue execution. 4. The double-checked version of slacker mode has changed the code to address the above inefficiencies.
public class SingletonObject {
private static SingletonObject singletonObject;
private SingletonObject() {
}
public static SingletonObject getInstance() {
if (singletonObject == null) {
synchronized (SingletonObject.class) {
if(singletonObject == null) { singletonObject = new SingletonObject(); }}}returnsingletonObject; }}Copy the code
Double check, that is, two times singletonObject == null judgment, compared with the above writing method, the efficiency is improved. However, it is also problematic. SingletonObject = new singletonObject (). This is not an atomic operation. Allocate memory for newly created objects. Call the constructor to initialize a member variable. Point singletonObject to the memory space of the new object. In step 3, the singletonObject is no longer null. However, the JVM just-in-time compiler is optimized for instruction reordering, so the execution order of the above three steps may be 1, 2, 3, or 1, 3, 2. When the latter order reaches 3, another thread executes the non-null judgment in if, and the singletonObject is no longer null. However, the value of a member variable is not initialized, and when another thread directly manipulates the incomplete initialized object, an error is guaranteed. 5. The enhanced version of the double-checked lazy mode fixes the problem above by using volatile to change the code.
public class SingletonObject {
private volatile static SingletonObject singletonObject;
private SingletonObject() {
}
public static SingletonObject getInstance() {
if (singletonObject == null) {
synchronized (SingletonObject.class) {
if(singletonObject == null) { singletonObject = new SingletonObject(); }}}returnsingletonObject; }}Copy the code
You might think that using volatile is for visibility across multiple threads, but this is not the case. Volatile is used to prohibit instruction reordering and to ensure that happens-before relationship, all writes will occur first before reads. It is important to note that the use of volatile prior to JDK 5 did not completely prevent instruction reordering due to defects in the Java memory model. The implementation of static inner classes is recommended.
public class SingletonObject implements Serializable {
private SingletonObject() {
}
private static class SingletonObjectHolder {
private static SingletonObject singletonObject = new SingletonObject();
}
public static SingletonObject getInstance() {
return SingletonObjectHolder.singletonObject;
}
protected Object readResolve() {
returnSingletonObjectHolder.singletonObject; }}Copy the code
Because of the JVM’s mechanics, this writing is thread-safe, lazy-loaded, and has no performance drawbacks. 7. Implementation of static code blocks
public class SingletonObject implements Serializable {
private static SingletonObject singletonObject;
private SingletonObject() {
}
static {
singletonObject = new SingletonObject();
}
public static SingletonObject getInstance() {
returnsingletonObject; }}Copy the code
Since a static block of code can only be executed once, this writing is thread-safe. 8. Implementation of enumerations use enumerations to implement singletons, which is thread-safe, but less used, and can be implemented by the reader.
Summary: The above several writing methods, according to the specific needs to choose, need to pay attention to the thread safety and efficiency problems.