# SingletonA intermediate-level guide to Singleton: before-and-after java code and diagrams for a CS student.

## The "Split-Brain" Problem

Imagine you are writing a program that manages a connection to a database. To save resources, your application should only ever have one active configuration for this connection. However, if different parts of your code can create their own versions of that configuration, you end up with a "split-brain" scenario: one part of the app thinks the database is at `url_a`, while another part thinks it is at `url_b`.

When multiple objects manage the same limited resource, they fight for control, leading to inconsistent data and wasted memory. This is what happens when we use a standard public constructor for sensitive, shared resources:

```java
public class DatabaseConfig {
    private String connectionUrl;

    // Public constructor allows multiple instances,
    // leading to inconsistent configuration state.
    public DatabaseConfig(String connectionUrl) {
        this.connectionUrl = connectionUrl;
    }

    public String getConnectionUrl() {
        return connectionUrl;
    }
}
```

In this structure, any developer can call `new DatabaseConfig(...)` at any time. Because there is no restriction on how many times the constructor is invoked, the application loses its "single source of truth."

```mermaid
classDiagram
    class ClientA {
        +connect()
    }
    class ClientB {
        +connect()
    }
    class DatabaseConfig {
        -String connectionUrl
        +DatabaseConfig(String)
    }
    ClientA --> DatabaseConfig : creates new instance
    ClientB --> DatabaseConfig : creates new instance
```

## Enforcing a Single Instance with Singleton

To solve this, we use the Singleton pattern. This creational design pattern ensures that a class has only one instance and provides a global point of access to it. To achieve this in Java, we use three specific tools:

1.  **`private` constructor**: By marking the constructor `private`, we prevent other classes from using the `new` keyword to create an instance.
2.  **`static` field**: We create a variable inside the class marked as `static`. This means the variable belongs to the class itself, not to any specific object, allowing it to persist for the life of the program.
3.  **`getInstance()` method**: Since we blocked the constructor, we provide a public `static` method that acts as the gatekeeper. If an instance already exists, it returns it; if not, it creates one.

```java
public class DatabaseConfig {
    private static DatabaseConfig instance;
    private final String connectionUrl;

    // Private constructor prevents external instantiation.
    private DatabaseConfig(String connectionUrl) {
        this.connectionUrl = connectionUrl;
    }

    // Thread-safe lazy initialization (synchronized for simplicity).
    public static synchronized DatabaseConfig getInstance(String connectionUrl) {
        if (instance == null) {
            instance = new DatabaseConfig(connectionUrl);
        }
        return instance;
    }

    public String getConnectionUrl() {
        return connectionUrl;
    }
}
```

Now, no matter how many times you ask for a `DatabaseConfig`, you will always receive the exact same object in memory.

```mermaid
sequenceDiagram
    participant Client as Main/Client
    participant Class as DatabaseConfig
    Client ->> Class: getInstance(url)
    alt instance is null
        Class ->> Class: new DatabaseConfig(url)
        Class -->> Client: returns new instance
    else instance exists
        Class -->> Client: returns existing instance
    end
```

## The Complexity of Concurrency

The implementation above uses the `synchronized` keyword. This is crucial in multi-threaded environments. Without it, a "race condition" can occur: two different threads might check `if (instance == null)` at the exact same microsecond. Both would find it to be true and both would proceed to create a new object, defeating the entire purpose of the Singleton.

By adding `synchronized` to our `getInstance` method, we ensure that only one thread can execute that logic at a time, protecting the integrity of our single instance.

```mermaid
flowchart TD
    Start([Call getInstance]) --> Check{Is instance null?}
    Check -- yes --> Lock[Enter synchronized block]
    Lock --> Create[Create new instance]
    Create --> Return[Return instance]
    Check -- no --> Return
    Return --> End([End])
```

## When to Use It, and When Not To

The Singleton pattern is powerful but can be overused. It is an excellent choice for managing shared resources like hardware drivers, logging services, or configuration managers where a duplicate would cause errors.

However, Singletons can make unit testing difficult because they introduce "global state" into your application. If one test modifies the Singleton, that change might leak into and break another test. In modern software engineering, many developers prefer **Dependency Injection**. Instead of a class reaching out to grab a Singleton, you "inject" the required instance through the constructor. This keeps your classes decoupled and makes them much easier to test with mock objects.

## Takeaways

- Use a `private` constructor to prevent external code from using `new`.
- A Singleton provides a single source of truth for shared resources.
- Always consider thread safety; use `synchronized` to prevent multiple threads from creating duplicate instances during initialization.

## Usage

**Demonstrating how the Singleton prevents multiple instances from existing.**

```java
// Before: Multiple configs create conflicting state
DatabaseConfig config1 = new DatabaseConfig("jdbc:mysql://localhost/db1");
DatabaseConfig config2 = new DatabaseConfig("jdbc:mysql://localhost/db2");

// After: Single source of truth
DatabaseConfig config1 = DatabaseConfig.getInstance("jdbc:mysql://localhost/db1");
DatabaseConfig config2 = DatabaseConfig.getInstance("jdbc:mysql://localhost/db2");
```
