post-image

Singleton Pattern – Một Object Duy Nhất

Tổng quan

Xin chào các bạn, hôm nay chúng ta sẽ tiếp tục làm quen với một mẫu Design Pattern mới: Singleton pattern. Cái tên đã nói lên tất cả, nếu muốn trong suốt chương trình chỉ tồn tại duy nhất một đối tượng của một lớp nào đó thì Singleton chính là điều chúng ta cần nghĩ đến đầu tiên. Ví dụ như các lớp Logging, Configuration, … Trong JDK tồn tại khá nhiều class dưới dạng Singleton như java.lang.Runtime, tất cả các chương trình Java đều có duy nhất một đối tượng Runtime để cho phép tương tác với environment của chương trình đang chạy. Hoặc java.awt.Desktop, java.awt.GraphicsEnvironment. Ngoài ra, Singleton Pattern còn được sử dụng kèm với các Pattern khác như: Abstract Factory, Builder, Prototype, Facade…

TẠO MỘT LỚP SINGLETON

Để gọi là một Singleton, hay nói đơn giản nếu bạn muốn tạo một lớp Singleton thì lớp đó phải đáp ứng một số yêu cầu sau:

  1. Private constructor: nhằm không để bên ngoài có thể khởi tạo các thể hiện của lớp một cách tùy ý.
  2. Private static instance: một lớp Singleton cần có một thể hiện static của chính nó.
  3. Public static function: một hàm public static để thực hiện việc khởi tạo và trả về thể hiện của lớp. Nếu một class muốn gọi các hàm (non-static) khác của lớp thì cần thực hiện thông qua hàm static này.

Mình sẽ giải thích kỹ hơn bằng các ví dụ dưới đây. Chúng ta sẽ cùng điểm qua một số cách khởi tạo lớp Singleton, những điểm cần lưu ý cũng như best practice khi áp dụng Singleton:
1. Eager initialization
2. Static block initialization
3. Lazy Initialization
4. Thread Safe Singleton
5. Bill Pugh Singleton Implementation
6. Using Reflection to destroy Singleton Pattern
7. Enum Singleton
8. Serialization and Singletons

1. EAGER INITIALIZATION

public class EagerInitializedSingleton {

private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();

//private constructor to avoid client applications to use constructor
private EagerInitializedSingleton(){}

public static EagerInitializedSingleton getInstance(){
return instance;
}
}

Đây là cách đơn giản và an toàn nhất. Tuy nhiên, cách này có một nhược điểm là nếu lớp này không được sử dụng sau đó thì việc khởi tạo sẽ trở thành phí phạm vì biến instance sẽ được khởi tạo ngay lúc chương trình chạy bất kể là có sử dụng đến lớp Singleton hay không. Một điểm nữa là không thể handle exception.

2. STATIC BLOCK INITIALIZATION

Cũng giống như Eager initialization ngoại trừ việc biến instance được khởi tạo trong khối static, ở ngay đây, exception sẽ được handle.

public class StaticBlockSingleton {

private static StaticBlockSingleton instance;

private StaticBlockSingleton(){}

//static block initialization for exception handling
static{
try{
instance = new StaticBlockSingleton();
}catch(Exception e){
throw new RuntimeException("Exception occured in creating singleton instance");
}
}

public static StaticBlockSingleton getInstance(){
return instance;
}
}

3. LAZY INITIALIZATION

public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;

private LazyInitializedSingleton(){}

public static LazyInitializedSingleton getInstance(){
if(instance == null){
instance = new LazyInitializedSingleton();
}
return instance;
}
}

Sử dụng cơ chế Lazy Initialization sẽ đảm bảo biến instance chỉ được khởi tạo nó thực sự được dùng đến, nghĩa là khi chúng ta cần đến lớp Singleton để xử lý công việc thông qua việc gọi hàm getInstance. Tuy nhiên Lazy Initialization lại để lộ ra một nhược điểm khi chạy trong môi trường multithreading. Khi có nhiều thread truy xuất đến hàm getInstance, sẽ xảy ra việc có nhiều đối tượng Singleton được tạo ra. Vì vậy chúng ta cần có các cơ chế implementation khác để đảm bảo tính thread-safe cho lớp Singleton. Cùng điểm qua 1 số cách dưới đây

4. THREAD SAFE SINGLETON

public class ThreadSafeSingleton {

private static ThreadSafeSingleton instance;

private ThreadSafeSingleton(){}

public static synchronized ThreadSafeSingleton getInstance(){
if(instance == null){
instance = new ThreadSafeSingleton();
}
return instance;
}

}

Bằng cách synchronized hàm getInstance, sẽ đảm bảo chỉ có duy nhất 1 thread được truy cập vào hàm getInstance và khởi tạo 1 đối tượng instance duy nhất. Đây là cách đơn giản và thông dụng nhất để đảm bảo tính thread-safe cho lớp Singleton. Tuy nhiên việc synchronized như vậy sẽ gây tốn chi phí về thời gian, vì tại 1 thời điểm, chỉ có duy nhất 1 thread được truy cập vào hàm getInstance để lấy ra (hoặc khởi tạo) đối tượng instance.

Thay vào đó, sử dụng kỹ thuật double checked locking sẽ giảm được chi phí cho hàm getInstance

public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
if(instance == null){
synchronized (ThreadSafeSingleton.class) {
if(instance == null){
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}

5. BILL PUGH SINGLETON IMPLEMENTATION

Phương pháp Bill Pugh sử dụng inner static helper class để khởi tạo đối tượng Singleton. Cách này được xem là hay và nên áp dụng nhất.

public class BillPughSingleton {

private BillPughSingleton(){}

private static class SingletonHelper{
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}

public static BillPughSingleton getInstance(){
return SingletonHelper.INSTANCE;
}
}

Ở đây, tuy là lớp SingletonHelper đã khởi tạo trực tiếp biến INSTANCE, nhưng biến INSTANCE chỉ được load vào memory khi nó được gọi thông qua hàm getInstance (các bạn tham khảo thêm về static inner class trong Java)

Ở trên là 1 số ví dụ về việc tạo ra 1 lớp Singleton trong Java. Còn rất nhiều vấn đề liên quan đến Singleton mà mình sẽ đề cập ở các bài viết khác như:

  • So sánh Singleton và Static class
  • Singleton trong môi trường multi JVM
  • Singleton sẽ không được đảm bảo nếu sử dụng Reflection
  • Serialize và deserialize đối tượng Singleton

Nguồn: https://topdev.vn/blog/singleton-pattern-mot-object-duy-nhat/

Leave a Reply

Your email address will not be published. Required fields are marked *