Tìm hiểu Java design pattern: Singleton

Chào mừng các bạn đã quay trở lại với thachleblog, trở lại chuyên mục Java nâng cao, hôm nay mình sẽ giới thiệu đến với các bạn Java design pattern đơn giản nhất là Singleton (mình đo độ đơn giản bằng độ dài của code implementation, hehe). Tuy là ngắn vậy nhưng có rất nhiều vấn đề chúng ta cần phải lưu ý để sử dụng hiệu quả nhất. Nào, chúng ta cùng bắt đầu nhé.

Giới thiệu

Trước hết mình sẽ khái quát về Singleton, Singleton thuộc loại Creational pattern (Java gồm 3 loại design pattern là Creational, Structural, Behavior). Do đó nó chức năng dùng để tạo object (đối tượng) kế thừa từ loại Creational.

Đặc điểm

Thiết kế Singleton đảm bảo cho class chỉ tạo được duy nhất một instance (instance là object trong vùng nhớ, ví dụ có 2 object cùng tham chiếu đến 1 vùng nhớ bằng cách clone thì ta hiểu rằng đó là một instance).

design pattern singleton

Hình minh họa cơ chế của design Singleton (nguồn internet)

Lợi ích

Vì đặc điểm của Singleton là chỉ cho phép class tạo duy nhất 1 instance, do đó Singleton được sử dụng để tối ưu resource.

Ấn tượng đáng nhớ nhất về Singleton là lúc còn sinh viên làm đồ án game, khi chưa dùng singleton, mỗi lần render hình ảnh mình lại load resouce, kết quả là game có mấy cái hình vài trăm Kb mà chạy 1 lúc tốn mấy trăm Mb RAM. Sử dụng Singleton giúp mình đảm bảo là resouce chỉ được load 1 lần lên RAM (có thể còn có cách khác, nhưng sử dụng Singleton thì cực kỳ đơn giản và hiệu quả)

Ngoài ra, design Singleton rất thường được sử dụng trong các libs, framework, ví dụ class Logger của log4j và Spring Beans (scope defaut của Spring là Singleton)…

Implement

Cơ bản nhất là dựa vào đặc điểm của static variable để tạo instance, đảm bảo duy nhất 1 instance của class được tạo trong suốt quá trình chương trình chạy (các bạn có thể xem thêm đặc điểm về từ khóa static tại đây).

Đồng thời định nghĩa constructor là private để ngăn không cho class khác kế thừa và tạo object

#1

Code minh họa

Class Singleton

  1. package thach.le.singleton;
  2. public class Singleton {
  3. private static Singleton instance = new Singleton();
  4. private Singleton() {}
  5. public static Singleton getInstance() {
  6. return instance;
  7. }
  8. }

Class Test

  1. package thach.le.singleton;
  2. public class TestSingleton {
  3. public static void main(String[] args) {
  4. Singleton singleton1 = Singleton.getInstance();
  5. Singleton singleton2 = Singleton.getInstance();
  6. System.out.println(singleton1);
  7. System.out.println(singleton2);
  8. }
  9. }

Kết quả: mặc dù mình khởi tạo 2 object, nhưng chỉ có duy nhất 1 instance được tạo (cái này cho vui ^.^)

singleton

Ưu điểm của cách implement này là gì? Rõ ràng quá rồi, đó là rất là đơn giản, đúng như cách mà chúng ta tư duy khi nghĩ về cách tạo duy nhất 1 instance của class.

Nhưng gượm đã, vì instance được tạo ngay tại thời điểm thực thi chương trình, trước cả lúc method getInstance() được gọi. Nên hơi lãng phí, chúng ta sẽ nghĩ ra cách để tiết kiệm vùng nhớ hơn. Tức là khi nào method getInstance() được gọi mới tạo instance. Cũng như chiến lược tiết kiệm tiền, chỉ mua những thứ cần, không nên cứ thích là mua 😀

2# Lazy instantiation

Với cách implement ở trên, instance được tạo ngay khi class được load. Do lãng phí, nên ta cần chỉnh sửa đoạn code trên một chút.

  1. package thach.le.singleton;
  2. public class Singleton {
  3. private static Singleton instance;
  4. private Singleton() {
  5. }
  6. public static Singleton getInstance() {
  7. //lazy instantiation
  8. if (instance == null) {
  9. instance = new Singleton();
  10. }
  11. return instance;
  12. }
  13. }

Rồi, giờ thì có vẻ tốt hơn rồi, instance chỉ được tạo khi method getInstance() được gọi. if(instance == null) để đảm bảo duy nhất 1 instance được gọi. Khổ nỗi, ở #1, tuy hơi lãng phí nhưng rõ ràng là thead – safe, #2 tuy chống được lãng phí nhưng lại không an toàn trong trường hợp có 2 (hoặc nhiều hơn) thread cùng thực thi đoạn code này. Các bạn cũng có thể xem thêm thread interference để hiểu đoạn code này không an toàn như nào. Ta bắt buộc phải xài synchronization để giải quyết vụ này thôi. Mình sẽ dùng synchronized block để tránh tình trạng “thắt nút cổ chai”. Đoạn code thread safe như sau:

  1. package thach.le.singleton;
  2. public class Singleton {
  3.  
  4. private static Singleton instance;
  5.  
  6. private Singleton() {
  7. }
  8.  
  9. public static Singleton getInstance() {
  10. //return fast
  11. if (instance == null) {
  12. //thread safe
  13. synchronized (Singleton.class) {
  14. //check null again to make sure return fast
  15. if (instance == null) {
  16. instance = new Singleton();
  17. }
  18. }
  19. }
  20. return instance;
  21. }
  22.  
  23. }

Kết

Trong Effective Java, tác giả John Blosch có khẳng định, tạo Singleton bằng enum là cách tốt nhất, ngoài ra còn có cách dùng nested class cũng vừa đảm bảo thead – safe và lazy loading, các bạn cũng có thể tham khảo thêm.

Vừa rồi là phần trình bày của mình về Singleton design pattern, hy vọng giúp các bạn hiểu và có thể vận dụng được Singleton trong các dự án. Rất mong nhận được ý kiến đóng góp của tất cả các bạn.

Xin cảm ơn.
Tham khảo thêm:
https://app.pluralsight.com/player?course=design-patterns-java-creational