banner
RustyNail

RustyNail

coder. 【blog】https://rustynail.me 【nostr】wss://ts.relays.world/ wss://relays.world/nostr

Design Pattern - Singleton Pattern

Sometimes, we want a class to only have one instance. For example, a school can only have one principal.

Preliminary#

To ensure that a class has only one instance, we need to hide its constructor: private Something(){}

Then, provide a method that can actually create/return the object. Here is an implementation:

public class Something {
    private static Something INSTANCE = null;
    private Something(){}

    public static Something getINSTANCE() {
        if (INSTANCE == null){
            INSTANCE = new Something();
        }
        return INSTANCE;
    }

}

This is a simple implementation of the singleton pattern. We can see that every time getINSTANCE() is called, we need to check if INSTANCE == null.

So we can also do this (if your singleton object is small and needed throughout runtime):

public class Something {
    private static Something INSTANCE = new Something();
    private Something(){}

    public static Something getINSTANCE() {
        return INSTANCE;
    }
}

Singleton Pattern in Multithreading#

The above implementation of the singleton pattern is not thread-safe. Because we cannot guarantee that the value of INSTANCE will not change. The solution to the thread problem is simple: add locks.

For example, we can add a lock to the getINSTANCE method:

public static synchronized Something getINSTANCE() {
    if (INSTANCE == null){
       INSTANCE = new Something();
    }
    return INSTANCE;
}

Or we can add a lock when checking for null:

public static Something getINSTANCE() {
    synchronized (Something.class){
        if (INSTANCE == null){
            INSTANCE = new Something();
        }
    }
    return INSTANCE;
}

However, there is a problem with this approach: there is a lock operation every time an instance is obtained, which consumes resources. If this method is frequently used, there will be performance issues.

Let's modify the code:

public static Something getINSTANCE() {
    if (INSTANCE == null){
        synchronized (Something.class){
            if (INSTANCE == null){
                INSTANCE = new Something();
            }
        }
    }
    return INSTANCE;
}

The advantage of this approach is that the lock is only added when INSTANCE is null. In other words, it only takes effect once.

Implementing Singleton Using Enum#

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

Using an enum to implement a singleton ensures that the object is only loaded when the class is actually used (using class loading mechanism). The loading process is synchronized and thread-safe.

The previous singleton is still not safe when it comes to serialization and deserialization, but for enums, it is directly specified that enum objects cannot be deserialized...

public abstract class Enum<E extends Enum<E>>  implements Comparable<E>, Serializable {  
   // omitted
  
    // Disallow deserialization of enum objects
    private void readObject(ObjectInputStream in) throws IOException,  
        ClassNotFoundException {  
            throw new InvalidObjectException("can't deserialize enum");  
    }  
  
    // Disallow deserialization of enum objects  
    private void readObjectNoData() throws ObjectStreamException {  
        throw new InvalidObjectException("can't deserialize enum");  
    }  
  
    // Enum classes cannot have a finalize method, and subclasses cannot override this method to ensure the uniqueness of object instances
    protected final void finalize() { }  
}  
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.