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 object. For example, a school can only have one principal.

Initial#

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

Then, we provide a public method to 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, it checks if INSTANCE == null.

So, we can also do it like this (if your singleton object is small enough and needed throughout the 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. It 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, this approach has a problem: every time we get an instance, there is a lock operation, which consumes resources. If this method is frequently used, it will have 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. It only takes effect once.

Implementing Singleton with Enum#

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

Using an enum to implement 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 implementation is still not safe when it comes to serialization and deserialization. However, 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 finalize method, subclasses cannot override this method to ensure object uniqueness
    protected final void finalize() { }  
}  
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.