Skip to content

Singleton Pattern Guide

Exactly one instance, reachable from everywhere

Builds on static and final

Stage 3core1 min read

The Singleton Solution

The refactored code makes sure only one instance of JudgeAnalytics exists. All callers share that object, so run and submit counts are truly global.


1. Key Improvements

Single Instance Guarantee

  • The Benefit: Every call to JudgeAnalytics.getInstance() returns the same instance. Run and submit state lives in exactly one place.
  • The Safety: You cannot create a second analytics object by accident, because the constructor is private.

Global State in One Place

  • The Benefit: Dashboards, reports, and rate limits read from one source of truth. "Total runs" and "total submits" now have a clear meaning.
  • The Impact: Any part of the system that calls getInstance() sees the same counts.

Lazy and Thread-Safe Initialization

  • The Benefit: The instance is built only when first needed (lazy). The static-holder idiom makes creation thread-safe without explicit locking.
  • The Result: Many threads can call it safely, and the instance is never built twice.

2. Structural Breakdown

ComponentResponsibility
SingletonThe class that allows only one instance of itself (private constructor + getInstance).
Static holderA nested class that holds the single instance. The JVM loads it the first time it is used.
Client (e.g. Main)Uses Singleton.getInstance() instead of new Singleton().

3. Comparison of "Before" vs "After"

FeatureBefore (Multiple Instances)After (Singleton)
Instantiationnew JudgeAnalytics() anywhere.Only via JudgeAnalytics.getInstance().
Number of instancesUnbounded; each caller can have its own.Exactly one.
Global countsNot possible; state is fragmented.Single object holds all run/submit data.
TestingHard to check "the" analytics, since none exists.One instance to pass in or mock (replace with a fake).

In this topic

After code2 files

After/JudgeAnalytics.java53 linesGitHub
package DesignPatterns.CreationalPatterns.SingletonDesignPattern.After;import java.util.concurrent.atomic.AtomicInteger;/** * Singleton: Ensures only one instance of JudgeAnalytics exists system-wide. * - Private constructor: no one can do "new JudgeAnalytics()". * - getInstance(): single point of access; creates the instance once, reuses it forever. * - Thread-safe: the static holder (JudgeAnalyticsHolder) is lazily loaded by the JVM. * * A singleton is shared by every thread, so its state must be thread-safe too. * "run++" is read-modify-write, so two threads can both read the same value and * one increment is lost. AtomicInteger does the whole step as one operation. */public class JudgeAnalytics {    private final AtomicInteger run = new AtomicInteger(0);    private final AtomicInteger submit = new AtomicInteger(0);    private JudgeAnalytics() {        // Prevent instantiation from outside.    }    public void countRun() {        run.incrementAndGet();    }    public void countSubmit() {        submit.incrementAndGet();    }    public int getRunCount() {        return run.get();    }    public int getSubmitCount() {        return submit.get();    }    /**     * Single point of access. The instance is created lazily when first requested     * and is thread-safe due to the class-loading semantics of the JVM.     */    public static JudgeAnalytics getInstance() {        return JudgeAnalyticsHolder.INSTANCE;    }    private static class JudgeAnalyticsHolder {        private static final JudgeAnalytics INSTANCE = new JudgeAnalytics();    }}
After/Main.java22 linesGitHub
package DesignPatterns.CreationalPatterns.SingletonDesignPattern.After;/** * All code uses JudgeAnalytics.getInstance(). There is only one instance; * run and submit counts are shared globally. */class Main {    public static void main(String[] args) {        JudgeAnalytics analytics = JudgeAnalytics.getInstance();        analytics.countRun();        analytics.countRun();        analytics.countSubmit();        // Same instance can be obtained anywhere; counts are global.        JudgeAnalytics same = JudgeAnalytics.getInstance();        System.out.println("Total runs: " + same.getRunCount()); // 2        System.out.println("Total submits: " + same.getSubmitCount()); // 1    }}

Done with this topic?

How well do you know it?