Implementing and Comparing Singleton Patterns in Java

Certain objects within a software system, such as application settings, should ideally have only one instance. Creating multiple instances leads to unnecessary memory consumption and resource waste. The following example demonstrates a standard configuration loader without singleton constraints.

Properties File (app.settings):

apiKey = XYZ123
maxConnections = 50

Standard Configuration Reader:

package core.config;

import java.io.*;
import java.util.Properties;

public class SystemSettings {
    private String apiKey;
    private String maxConn;

    public SystemSettings() {
        loadProperties();
    }

    private void loadProperties() {
        Properties props = new Properties();
        try (InputStream input = getClass().getResourceAsStream("app.settings")) {
            if (input == null) {
                System.out.println("Sorry, unable to find app.settings");
                return;
            }
            props.load(input);
            this.apiKey = props.getProperty("apiKey");
            this.maxConn = props.getProperty("maxConnections");
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public String getApiKey() { return apiKey; }
    public String getMaxConn() { return maxConn; }
}

Client Usage:

package core.config;

public class AppRunner {
    public static void main(String[] args) {
        SystemSettings settings1 = new SystemSettings();
        SystemSettings settings2 = new SystemSettings();
        
        System.out.println(settings1.getApiKey());
        System.out.println(settings2.getApiKey());
    }
}

The issue with this approach is that every invocation of new SystemSettings() triggers I/O operations and creates a distinct object in memory, even though the data remains identical. A more efifcient approach is to ensure the class is instantiated only once.

Definition: The Singleton Pattern ensures a class has only one instance and provides a global point of access to it.

Eager Initialization (The "Eager" Approach)

In this variant, the instance is created at the time the class is loaded. This is straightforward and thread-safe by default.

package core.config;

import java.io.*;
import java.util.Properties;

public class EagerSettings {
    // Instance created immediately when the class is loaded
    private static final EagerSettings INSTANCE = new EagerSettings();

    private String apiKey;
    private String maxConn;

    // Private constructor prevents external instantiation
    private EagerSettings() {
        loadProperties();
    }

    private void loadProperties() {
        Properties props = new Properties();
        try (InputStream input = getClass().getResourceAsStream("app.settings")) {
            if (input != null) {
                props.load(input);
                this.apiKey = props.getProperty("apiKey");
                this.maxConn = props.getProperty("maxConnections");
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public static EagerSettings getInstance() {
        return INSTANCE;
    }

    public String getApiKey() { return apiKey; }
    public String getMaxConn() { return maxConn; }
}

Lazy Initialization (The "Lazy" Approach)

Here, the instance is created only when its first requested. The basic implementation below is not thread-safe.

package core.config;

import java.io.*;
import java.util.Properties;

public class LazySettings {
    private static LazySettings instance;
    private String apiKey;
    private String maxConn;

    private LazySettings() {
        loadProperties();
    }

    private void loadProperties() {
        // ... loading logic similar to above ...
    }

    public static LazySettings getInstance() {
        if (instance == null) {
            instance = new LazySettings();
        }
        return instance;
    }
}

Thread-Safe Lazy Initialization

To make the lazy approach safe for multithreaded environments, synchronization is required. A common way is using the synchronized keyword or volatile.

package core.config;

public class ThreadSafeSettings {
    private static volatile ThreadSafeSettings instance;
    private String apiKey;
    private String maxConn;

    private ThreadSafeSettings() {
        // Initialization logic
    }

    public static ThreadSafeSettings getInstance() {
        if (instance == null) {
            synchronized (ThreadSafeSettings.class) {
                if (instance == null) {
                    instance = new ThreadSafeSettings();
                }
            }
        }
        return instance;
    }
}

Comparison: Eager vs. Lazy

Feature Eager Initialization Lazy Initialization
Instance Creation At class loading time. When getInstance() is first called.
Performance Faster access (no null checks/locking), but higher startup memory. Slower first access (due to creation), saves memory if never used.
Thread Safety Inherently safe (Classloader guarantee). Requires manual handling (synchronization).

Variant: Limited Instance Pool

The concept of Singleton can be extended to control a specific number of instances (e.g., limiting to 3 objects) using a registry.

package core.config;

import java.util.HashMap;
import java.util.Map;

public class PooledConfig {
    private static final int MAX_INSTANCES = 3;
    private static Map<Integer, PooledConfig> instanceMap = new HashMap<>();
    private static int counter = 1;

    private PooledConfig() { }

    public static PooledConfig acquire() {
        if (!instanceMap.containsKey(counter)) {
            instanceMap.put(counter, new PooledConfig());
        }
        
        PooledConfig current = instanceMap.get(counter);
        
        counter++;
        if (counter > MAX_INSTANCES) {
            counter = 1;
        }
        
        return current;
    }
}

Running a loop to acquire 10 instances of PooledConfig will result in only 3 distinct objects being created, cycling through them based on the counter logic.

Tags: Design Patterns Singleton Pattern java Creational Patterns Software Architecture

Posted on Thu, 08 Oct 2026 16:41:01 +0000 by prueba123a