
1. Ý tưởng cốt lõi của Singleton
Singleton là một creational pattern.
Mục tiêu chính:
1. Chỉ có một instance duy nhất của class trong JVM.
2. Cung cấp global access point – một cách truy cập toàn cục đến instance đó.
Về mặt code, thay vì:
MyService service = new MyService(); // mỗi chỗ new ra 1 object mới
Bạn dùng:
MyService service = MyService.getInstance(); // luôn cùng 1 object
Hai mục tiêu kỹ thuật:
- Chặn không cho code bên ngoài `new` thêm instance.
- Đảm bảo mọi nơi gọi `getInstance()` đều nhận cùng một object.
2. Cấu trúc cơ bản Singleton trong Java
Trong Java, một Singleton design pattern in Java cơ bản thường có 3 phần:
1. Constructor private
- Chặn code bên ngoài `new` class đó.
2. Biến static private để giữ instance
- Đây là nơi lưu instance duy nhất.
3. Phương thức static public getInstance()
- Trả về instance duy nhất đó.
Skeleton đơn giản:
public class MySingleton {
// 1. Biến static private giữ instance
private static MySingleton instance;
// 2. Constructor private
private MySingleton() {
// init nếu cần
}
// 3. Phương thức static public trả về instance
public static MySingleton getInstance() {
if (instance == null) {
instance = new MySingleton();
}
return instance;
}
}
Về mặt cấu trúc, đây là “hình mẫu” mà interview thường mong đợi.
Nhưng phiên bản này chưa thread-safe khi có multi-thread (phần lazy sẽ nói rõ).
3. Eager Singleton: đơn giản, thread-safe
3.1. Eager initialization là gì?
Eager initialization nghĩa là tạo instance ngay khi class được load, không đợi ai gọi getInstance().
Ưu điểm: với Java, quá trình load class là thread-safe, nên eager Singleton mặc định là thread-safe mà không cần synchronized.
3.2. Ví dụ Eager Singleton
public class AppConfig {
// Tạo instance ngay khi class được load
private static final AppConfig INSTANCE = new AppConfig();
// Constructor private
private AppConfig() {
// ví dụ: đọc config từ file, env, v.v.
}
// Global access point
public static AppConfig getInstance() {
return INSTANCE;
}
// Ví dụ method
public String getAppName() {
return "My App";
}
}
Tại sao cách này tốt:
- **Thread-safe** mà không cần synchronized.
- Rất dễ đọc, dễ giải thích cho interviewer.
- Phù hợp cho các object “nhẹ” hoặc setup một lần cho cả app.
Trade-off:
- Instance được tạo ra **dù không ai dùng**.
- Nếu object nặng, hoặc có nhiều Singleton kiểu này, có thể lãng phí.
Trong đa số câu hỏi phỏng vấn về Singleton design pattern in Java, eager Singleton như trên là câu trả lời an toàn và đúng.
4. Lazy Singleton và vấn đề thread-safety
4.1. Lazy initialization là gì?
Lazy initialization nghĩa là chỉ tạo instance khi lần đầu cần dùng.
Nếu chương trình không bao giờ dùng tới Singleton đó thì không tốn cost tạo.
4.2. Lazy Singleton “ngây thơ” (không thread-safe)
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
// init
}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton(); // vấn đề nằm đây
}
return instance;
}
}
Trong chương trình single-thread, đoạn này trông có vẻ ổn.
Issue xuất hiện khi có multi-thread cùng gọi getInstance().
Kịch bản lỗi:
- Thread A check `instance == null` → đúng.
- Thread B cũng check `instance == null` → vẫn đúng (A chưa kịp gán).
- A tạo `new LazySingleton()`.
- B cũng tạo `new LazySingleton()`.
- Kết quả: có **2 instance** khác nhau.
Như vậy, lazy Singleton kiểu này không đảm bảo Singleton trong môi trường multi-thread.
Câu trả lời ngắn gọn cho phỏng vấn:
Lazy Singleton với check if (instance == null) không thread-safe vì nhiều thread có thể đồng thời tạo nhiều instance khi instance đang là null.
5. Lazy Singleton thread-safe đơn giản
Có nhiều kỹ thuật nâng cao hơn (double-checked locking với `volatile`, inner static class kiểu Bill Pugh), nhưng với fresher, bạn nên nắm trước phiên bản synchronized đơn giản.
5.1. Dùng synchronized trên getInstance()
public class SynchronizedLazySingleton {
private static SynchronizedLazySingleton instance;
private SynchronizedLazySingleton() {
// init
}
public static synchronized SynchronizedLazySingleton getInstance() {
if (instance == null) {
instance = new SynchronizedLazySingleton();
}
return instance;
}
}
synchronized ở đây có nghĩa:
- Chỉ **một thread** được vào `getInstance()` tại một thời điểm.
- Nếu có 2 thread gọi cùng lúc, thread thứ hai phải chờ thread thứ nhất xong.
- Nhờ vậy, chỉ tạo đúng **1 instance**.
Ưu điểm:
- Dễ hiểu, code đơn giản.
- Đạt mục tiêu **thread-safe singleton**.
Nhược điểm:
- `synchronized` có overhead.
- Nếu `getInstance()` được gọi cực kỳ nhiều lần trong code hot-path → có thể ảnh hưởng performance.
- Với đa số bài tập, demo, dự án nhỏ, overhead này chấp nhận được.
5.2. Singleton bằng enum (nhắc nhanh)
Cách khác gọn, an toàn hơn, hay được senior dùng là enum singleton:
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
// logic
}
}
Cách dùng:
EnumSingleton singleton = EnumSingleton.INSTANCE;
singleton.doSomething();
Vì sao nhiều người thích:
- **Thread-safe** theo cơ chế enum + class loading của Java.
- Tự xử lý ổn serialization.
- Code rất ngắn, ít boilerplate.
Tuy nhiên, với người mới, enum Singleton có thể hơi “lạ” so với pattern “class + getInstance()”.
Để phỏng vấn, nên biết cả:
- Phiên bản class-based với `getInstance()`.
- Enum-based Singleton như một lựa chọn hiện đại, gọn và an toàn.
6. Khi nào nên dùng Singleton
Một cách ngắn gọn: dùng Singleton design pattern in Java khi bạn thật sự cần đúng một instance dùng chung trong toàn hệ thống.
6.1. Khi chỉ nên có đúng một “nguồn sự thật”
Ví dụ điển hình:
- Config toàn hệ thống (`ConfigManager`, `AppConfig`).
- Logger đơn giản cho project nhỏ (`Logger`).
- Cache in-memory dùng chung.
Các loại object này về logic:
Nếu có 2–3 instance khác nhau, rất dễ gây rối.
6.2. Khi cần global access point
Khi nhiều nơi trong code cần dùng chung một object, mà việc truyền nó qua constructor / method tham số quá phiền phức, Singleton là cách nhanh gọn.
Ví dụ:
Project bài tập nhỏ, chưa dùng DI, chưa tách module rõ, có một AppConfig hoặc SimpleLogger dưới dạng Singleton là chấp nhận được.
6.3. Khi muốn kiểm soát việc khởi tạo
Singleton cho phép bạn:
- Kiểm soát chặt **thời điểm tạo instance**.
- Đảm bảo không ai vô tình `new` class đó.
Câu trả lời gọn cho phỏng vấn:
Em dùng Singleton khi cần một instance duy nhất dùng chung trong toàn bộ ứng dụng, ví dụ config manager, logger đơn giản hoặc cache chung.
7. Khi nên tránh dùng Singleton
Singleton rất dễ “lạm dụng”.
Trong code thực tế, quá nhiều Singleton thường là dấu hiệu design đang xấu dần.
7.1. Gây khó khăn cho unit test
Khi class A dùng:
MySingleton.getInstance().doSomething();
Muốn test A:
- Khó thay thế `MySingleton` bằng mock/fake.
- Không dễ inject implementation khác vào.
Trong design tốt hơn (đặc biệt với các framework có dependency injection), bạn truyền dependency qua constructor:
public class A {
private final Service service;
public A(Service service) {
this.service = service;
}
}
Điều này test-friendly hơn nhiều so với việc A tự gọi Singleton.getInstance().
7.2. Tạo global state ẩn
Singleton thực chất là global state có áo OOP.
Vấn đề:
- Bất cứ đâu cũng truy cập được.
- Bất cứ đâu cũng có thể thay đổi state bên trong.
- Debug rất khổ: “không biết đoạn nào đã chỉnh sửa Singleton”.
Khi codebase lớn, global state là một trong những nguồn bug đau đầu nhất.
7.3. Dùng Singleton vì… lười thiết kế
Cảm giác rất dễ bị nghiện:
SomeManager.getInstance().doWork();
AnotherService.getInstance().callSomething();
Thay vì suy nghĩ:
- Object này nên được tạo ở đâu?
- Ai là “owner” tự nhiên của nó?
- Có thể truyền nó qua constructor/method không?
Trong nhiều trường hợp, chỉ cần một object được tạo ở main() hoặc lớp “Application” rồi truyền xuống dưới là đủ, không cần thiết kế Singleton.
7.4. Độ phức tạp multi-thread
High-level:
Singleton + multi-thread = dễ sai nếu không hiểu rõ.
Để thread-safe đúng chuẩn, bạn phải:
- Dùng `synchronized`, hoặc
- Dùng double-checked locking + `volatile`, hoặc
- Dùng enum Singleton.
Người mới rất dễ viết sai 1–2 chi tiết và tạo bug khó đoán.
Với dự án thực, nếu không chắc, thường tốt hơn là né global Singleton và dùng cách quản lý object rõ ràng hơn.
8. Một số câu hỏi phỏng vấn Java về Singleton
Dưới đây là vài câu hỏi phỏng vấn Java phổ biến về Singleton design pattern in Java, kèm câu trả lời ngắn gọn.
Câu 1. Singleton design pattern trong Java là gì?
Đáp:
Singleton là một creational pattern đảm bảo một class chỉ có một instance duy nhất trong JVM và cung cấp global access point đến instance đó, thường qua method static getInstance().
Câu 2. Làm sao implement Singleton cơ bản trong Java?
Đáp:
Dùng constructor private, một biến static private giữ instance, và một method static public trả về nó. Ví dụ eager:
public class MySingleton {
private static final MySingleton INSTANCE = new MySingleton();
private MySingleton() {}
public static MySingleton getInstance() {
return INSTANCE;
}
}
Câu 3. Tại sao lazy Singleton cơ bản không thread-safe?
Đáp:
Vì nhiều thread có thể cùng lúc thấy instance == null rồi mỗi thread tự tạo new.
Kết quả là có nhiều instance khác nhau, vi phạm nguyên tắc Singleton.
Câu 4. Làm sao tạo thread-safe singleton?
Đáp:
Cách đơn giản nhất: dùng synchronized trên getInstance():
public static synchronized MySingleton getInstance() {
if (instance == null) {
instance = new MySingleton();
}
return instance;
}
Hoặc dùng eager Singleton, hoặc enum Singleton, cả hai đều thread-safe dựa trên cơ chế class loading của Java.
Câu 5. Nhược điểm của Singleton là gì?
Đáp:
Singleton tạo global state, làm unit test khó hơn, tăng coupling (code phụ thuộc chặt vào implementation cụ thể), và nếu lạm dụng sẽ khiến design khó mở rộng, khó maintain.
9. Một vài lời khuyên thực tế cho fresher
- **Học thuộc 1–2 implementation chuẩn**
- Eager Singleton (dễ, thread-safe).
- Lazy + `synchronized` (dễ, thread-safe, lazy).
- Nếu rảnh, thêm enum Singleton để ghi điểm.
- **Đừng biến mọi thứ thành Singleton**
- Nếu thấy mình định làm 3–4 class Singleton, nên dừng lại xem có cách design khác không.
- Hãy ưu tiên truyền dependency qua constructor.
- **Hiểu trade-off, không chỉ thuộc code mẫu**
- Biết giải thích: eager vs lazy, thread-safety, nhược điểm global state.
- Đây là thứ interviewer rất thích nghe ở junior có suy nghĩ.
Nếu bạn tự tay code lại vài phiên bản Singleton, chạy thử trong project nhỏ, rồi tập trả lời miệng các câu hỏi trên, bạn sẽ nắm rất chắc Singleton design pattern in Java cho cả phỏng vấn lẫn khi áp dụng ở những dự án Java cơ bản.