Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
146: The Singleton Pattern in Java
In a perfect world, every object in your Java app would have a clear, short lifecycle. But in the real world, you often run into a scenario where having more than one instance of a class is not just redundant, but actually dangerous. Think about a database configuration manager. You don't want five different objects reading the same config.properties file from the disk and holding five identical copies of those settings in memory. You want one source of truth.
Why we can't just use "new"
The core idea of the Singleton pattern is simple: ensure a class has only one instance and provide a global point of access to it. To do this, we have to take away the user's ability to call new MyClass(). I'll do this by making the constructor private.
public class DatabaseConfig {
private String dbUrl = "jdbc:mysql://localhost:3306/mydb";
// Private constructor prevents other classes from instantiating this
private DatabaseConfig() {}
public String getDbUrl() {
return dbUrl;
}
}
Now we have a problem: the class is useless because nobody can actually create it. We need a way to hand out the instance. I'll add a static variable to hold the instance and a static method to return it.
Building the basic manager
I'll implement what's called "Lazy Initialization." Instead of creating the instance the moment the app starts, I'll wait until the first time someone actually asks for it. This saves resources if the configuration is never actually needed during a particular execution path.
public class DatabaseConfig {
private String dbUrl = "jdbc:mysql://localhost:3306/mydb";
private static DatabaseConfig instance;
private DatabaseConfig() {}
public static DatabaseConfig getInstance() {
if (instance == null) {
instance = new DatabaseConfig();
}
return instance;
}
public String getDbUrl() {
return dbUrl;
}
}
The race condition I forgot about
Here is where I usually trip up when I'm rushing a prototype. The code above works perfectly in a single-threaded environment. But imagine this is running in a Spring Boot app or a web server with multiple threads. Two threads could call getInstance() at the exact same millisecond. Both see that instance == null is true, and both proceed to call new DatabaseConfig().
Suddenly, you have two "Singletons." This is a classic race condition. I could just add the synchronized keyword to the getInstance() method, but that's a performance killer because every single call to get the config would have to wait in line, even after the instance has already been created.
Making it thread-safe and efficient
To fix this without destroying performance, I'm going to use the "Initialization-on-demand holder" idiom. It uses a private static inner class. Java guarantees that the inner class is not loaded into memory until it is referenced in the getInstance() method, and the JVM handles the thread safety of class loading for us automatically. It's cleaner than double-checked locking and much faster than synchronizing the whole method.
public class DatabaseConfig {
private String dbUrl = "jdbc:mysql://localhost:3306/mydb";
private DatabaseConfig() {}
// This inner class is only loaded when getInstance() is called
private static class Holder {
private static final DatabaseConfig INSTANCE = new DatabaseConfig();
}
public static DatabaseConfig getInstance() {
return Holder.INSTANCE;
}
public String getDbUrl() {
return dbUrl;
}
}
Now, no matter how many threads hit this at once, the JVM ensures only one instance of DatabaseConfig is ever created. If you want the absolute simplest version and don't care about lazy loading, you could also just use an enum, which is the way Joshua Bloch recommends in Effective Java, but the holder pattern is a great way to understand how Java's class loading works to your advantage.
📋 Practical Task
Implementing a Global AppSettings Singleton
You are building a desktop application that needs to track user preferences (like themeColor and fontSize) across multiple different screens. If each screen creates its own settings object, changes made on the 'Settings' screen won't reflect on the 'Main' screen.
Your Task:
- Create a class named
AppSettings. - Implement the Singleton pattern using the Static Inner Holder approach discussed in the lesson.
- Add two private fields:
String themeColor(default to "Light") andint fontSize(default to 12). - Provide getter and setter methods for both fields.
- In a separate
Mainclass, instantiate two variables by callingAppSettings.getInstance(). Change thethemeColorusing the first variable, then print thethemeColorof the second variable to verify they are referencing the exact same object.
There are no comments for now.