menu

VanThuong.Com

Blog học lập trình Java và SEO miễn phí.

Tìm hiểu Java Singleton Pattern trong 5 phút

Posted by on
Singleton pattern là một trong những Design Pattern dễ nhất của Java. Nó thuộc nhóm Creational pattern và là cách tốt nhất sử dụng để khởi tạo một đối tượng.
Tìm hiểu Java Singleton Pattern trong 15 phút

Pattern này bao gồm một lớp duy nhất chịu trách nhiệm tạo ra một đối tượng nhưng phải đảm bảo rằng chỉ có đối tượng duy nhất được tạo ra. Lớp này cung cấp cho chúng ta cách truy cập vào đối tượng duy nhất của nó và có thể được truy cập trực tiếp mà không cần phải khởi tạo đối tượng của lớp.

Để dễ hiểu hơn bạn có thể hình dung như thế này, bạn có một lớp A và bạn chỉ muốn tạo ra "một và chỉ một" đối tượng của lớp A để sử dụng trong chương trình của bạn. Singleton Pattern sẽ giúp bạn thực hiện thực hiện được điều này và qua bài viết bên dưới đây hi vọng rằng có thể giúp bạn hiểu rõ hơn về nó.

Singleton Eager Initialization

Khi bạn sử dụng cách này thì Instance variable (biến thể hiện) của lớp sẽ được tạo trước khi nó thật sự cần thiết. Và instance này sẽ được khởi tạo ngay khi mà hệ thống của bạn bắt đầu chạy.

public class EagerSingleton {
  private static volatile EagerSingleton instance = new EagerSingleton();
 
  private EagerSingleton() {
    // private constructor
  }
 
  public static EagerSingleton getInstance() {
    return instance;
  }
}
Với cách này thì nó hoạt động hoàn toàn ổn nhưng có một hạn chế. Instance được khởi tạo bất kể nó có cần thiết tại thời điểm Runtime hay không. Giả sử instance này không phải là một đối tượng có kích thước lớn thì nó hoàn ổn.

Singleton Lazy Initilization

Để giải quyết vấn đề cho việc khởi tạo instance ngay khi chương trình khởi chạy của Eager thì Singleton Lazy được sinh ra. Nó được sử dụng như là một chiến thuật trì hoãn khởi tạo đối tượng cho đến khi nó thật sự cần thiết và được gọi ở lần đầu tiên.

public final class LazySingleton {
  private static volatile LazySingleton instance = null;
 
  private LazySingleton() {
    // private constructor
  }
 
  public static LazySingleton getInstance() {
    if (instance == null) {
      synchronized (LazySingleton.class) {
        instance = new LazySingleton();
      }
    }
    return instance;
  }
}
Trong lần gọi đầu tiên, phương thức getInstance() sẽ thực hiện kiểm tra xem instance đã được khởi tạo hay chưa. Nếu như chưa (instance == null) được khởi tạo thì nó sẽ thực hiện tạo ra đối tượng mới cho instance và trả về tham chiếu của nó. Ngược lại, nếu như instance đã được tạo rồi thì nó chỉ cần trả về tham chiếu của nó mà thôi.

Mặc dù vậy, giải pháp này vẫn chưa thực sự hoàn hảo bởi nó còn tồn tại một hạn chế khác. Giả sử, có 2 thread là Thread1 và Thread2 cùng khởi tạo instance và vượt qua được điều kiện (instance == null). Lúc này, cả 2 thread đều đã xác định rằng biến instance là null, do đó chúng cần khởi tạo đối tượng cho instance. 2 Thread này sẽ tuần tự đi vào synchronized block và khởi tạo các instance. Cuối cùng chúng ta có 2 instance trên hệ thống => Tan nát idea rồi.

Giải pháp

Chúng ta có thể giải quyết vấn đề vừa nêu trên bằng cách sử dụng "double-checked locking". Như ở trên đã đề cập thì 2 Thread sẽ tuần tự đi vào synchronized block để khởi tạo biến instance thì ở đây chúng ta sẽ kiếm tra lại 1 lần nữa trước khi khởi tạo đối tượng như sau:
public class LazySingleton {
  private static volatile LazySingleton instance =  null;
 
  private LazySingleton() {
  }
 
  public static LazySingleton getInstance() {
    if (instance == null) {
      synchronized (LazySingleton.class) {
        // Double check
        if (instance == null) {
          instance = new LazySingleton();
        }
      }
    }
    return instance;
  }
}

Initialization on Demand Holder

Phương pháp này được viết bởi Bill Pugh của trường đại học University of Maryland. Khi sử dụng nó thì chúng ta không cần phải synchronized mà vẫn đảm bảo được thread safe và tăng hiệu suất.
public class Singleton {  
  private Singleton() {
  }

  private static class LazyHolder {
    private static final Singleton INSTANCE = new Singleton();
  }

  public static Singleton getInstance() {
    return LazyHolder.INSTANCE;
  }
}
Như bạn có thể thấy rằng, LazyHolder inner class sẽ không được khởi tạo cho đến khi thật sự cần thiết và bạn có thể sử dụng các phần tử static khác của lớp Singleton.

Sử dụng Enum

Nếu như bạn đã đọc qua cuốn Effective Java sẽ thấy Joshua Bloch đã khẳng định rằng "sử dụng enum type là cách tốt nhất để triển khai Singleton" cho bất kì ngôn ngữ nào mà hỗ trợ enums.

Trong Java docs, Enum được ngầm định hỗ trợ thread safety và đảm bảo chỉ có duy nhất một instance được tạo. Đây cũng là cách tốt nhất để bạn triển khai Singleton với chi phí ít nhất.
public enum EnumSingleton {
  INSTANCE;
  public void someMethod(String param) {
    // some class member
  }
}

Ứng dụng của Singleton

  • Vì class Singleton chỉ tồn tại 1 Instance nên nó thường được dùng cho các trường hợp giải quyết các bài toán cần truy cập vào các shared resource hoặc implement cho các Logger class, Configuration class hoặc DAO.
  • Một số design pattern khác cũng sử dụng Singleton để triển khai: Factory, Abstract Factory ...
Đăng bình luận

menu