[Design Patterns] Singleton
ĐÂY LÀ NOTE ĐỂ GHI NHỚ, KHÔNG PHẢI MỘT BÀI VIẾT!
1. Singleton pattern là gì?
- Đảm bảo rằng chỉ có 1 instance duy nhất được tạo ra
- Cung cấp một global method để có thể access vào instance này
2. Cách implement
2.1. Eager initialization
Singleton instance được khởi tạo ngay khi class được sử dụng trong ứng dụng.
Ví dụ trong implementation bên đưới, instance này sẽ được khởi tạo ngay cả khi một class khác không gọi hàm getInstance()
mà chỉ sử dụng biến NAME
.
Đây cũng là một nhược điểm của cách implement này: Instance có thể được khởi tạo mà không dùng tới, gây lãng phí tài nguyên.
package com.jacktt.designpatterns.creational;
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton INSTANCE = new EagerInitializedSingleton();
public static final String NAME = "EagerInitializedSingleton";
// Private constructor to avoid client applications initializing new instance
private EagerInitializedSingleton() {
System.out.println("EagerInitializedSingleton instance is created!");
}
public static EagerInitializedSingleton getInstance() {
return INSTANCE;
}
}
2.2. Static block initialization
Trước tiên, chúng ta cần xem static block trong Java được thực thi khi nào:
- Static blocks are called as soon as class is loaded even before instance of class is created (i.e. before constructor is called).
- static blocks executes before instance blocks (instance block is a block that is in class without any prefix).
public class StaticBlockSingleton {
public static final String NAME = "StaticBlockSingleton";
private static final StaticBlockSingleton INSTANCE;
private StaticBlockSingleton() {
throw new RuntimeException("Exception occurred in creating singleton instance");
}
{
// This is instance block
}
// Static block initialization for exception handling
static {
INSTANCE = new StaticBlockSingleton();
}
public static StaticBlockSingleton getInstance() {
return INSTANCE;
}
}
TODO:
Mình chưa research được docs nào nói về việc catch exception được throw từ static block. Theo mình test thì nó sẽ throw exception khi chúng ta access vào một biến mà static block sử dụng.
Ví dụ trong implementation trên, nếu hàm main chỉ access vào biến NAME thì không lỗi, nhưng nếu access vào biến INSTANCE hoặc nếu biến NAME được sử dụng trong static block thì dòng nào ở hàm main dùng biến này sẽ gây ra lỗi.
2.3. Lazy Initialization
Lazy Initialization tức là chỉ init instance getInstance()
được gọi ít nhất 1 lần.
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
private LazyInitializedSingleton() {
}
public static LazyInitializedSingleton getInstance() {
if (instance == null) {
instance = new LazyInitializedSingleton();
}
return instance;
}
}
Tuy nhiên cách implement này vẫn chưa đảm bảo thread-safe.
2.4. Thread-safe With Lazy Initialization
package com.jacktt.designpatterns.creational;
public class ThreadSafeSingleton {
// The field must be declared volatile so that double check lock would work correctly.
private static volatile ThreadSafeSingleton instance;
private ThreadSafeSingleton() {
}
public static ThreadSafeSingleton getInstance(String value) {
// The approach taken here is called double-checked locking (DCL). It
// exists to prevent race condition between multiple threads that may
// attempt to get singleton instance at the same time, creating separate
// instances as a result.
//
// It may seem that having the `result` variable here is completely
// pointless. There is, however, a very important caveat when
// implementing double-checked locking in Java, which is solved by
// introducing this local variable.
//
// You can read more info DCL issues in Java here:
// https://refactoring.guru/java-dcl-issue
ThreadSafeSingleton result = instance;
if (result != null) {
return result;
}
synchronized(ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
}
}
3. Real-world examples
- Database Connection: When an application needs to connect to a database, it is often beneficial to have a single, shared connection that multiple components can utilize. A DatabaseConnection class can be implemented to provide a centralized point for establishing and managing the database connection.
- Config: Applications often require a centralized static configuration to store settings and properties.
- Cache Manager: A singleton cache ensures consistency in data storage and retrieval, as all parts of the application interact with the same instance of the cache
4. Pros and Cons
References
All rights reserved