Mẫu thiết kế Singleton đảm bảo rằng một lớp chỉ có một thể hiện (instance) duy nhất. Do thể hiện này có tiềm năng sử dụng trong suốt chương trình, nên mẫu thiết kế Singleton cũng cung cấp một điểm truy cập toàn cục đến nó.
Hộp thoại Find là một ví dụ điển hình của mẫu thiết kế Singleton. Dù bạn chọn menu hoặc nhấn phím tắt (Ctrl +F) nhiều lần thì cũng chỉ có duy nhất một hộp thoại xuất hiện.
Hộp thoại Find là một ví dụ điển hình của mẫu thiết kế Singleton. Dù bạn chọn menu hoặc nhấn phím tắt (Ctrl +F) nhiều lần thì cũng chỉ có duy nhất một hộp thoại xuất hiện.
1. Cài đặt
Mẫu thiết kế Singleton đơn giản và dễ áp dụng, chỉ cần bổ sung vài dòng lệnh trong lớp muốn chuyển thành Singleton.
– Dữ liệu thành viên instance (private và static) là đối tượng duy nhất của lớp Singleton.
– Constructor của lớp Singleton được định nghĩa thành protected hoặc private để người dùng không thể tạo thực thể trực tiếp từ bên ngoài lớp.
– Phương thức getInstance() dùng để khởi tạo đối tượng duy nhất, định nghĩa thành public và static. Client chỉ dùng getInstance() để tạo đối tượng cho lớp Singleton.
– Thực hiện khởi tạo chậm (lazy initialization) trong getInstance(): chỉ khi gọi phương thức getInstance() mới khởi tạo đối tượng. Phương thức này trả về một thể hiện mới hay null tùy thuộc vào một tham số kiểu boolean dùng như cờ hiệu bào xem lớp Singleton đã tạo thể hiện hay chưa.
Trong chế độ multithreading, mẫu thiết kế Singleton có thể làm việc không tốt: do getInstance() không an toàn thread, hai thread có thể gọi phương thức sinh đối tượng cùng một thời điểm và hai thể hiện sẽ được tạo ra. Nếu đồng bộ (synchronized) phương thức getInstance() để an toàn thread sẽ dẫn đến giảm hiệu suất chương trình.
Có nhiều giải pháp cho vấn đề này: double-checked locking, enum, class loader (static block initialization).
– double-checked locking: kiểm tra sự tồn tại thể hiện của lớp, với sự hổ trợ của đồng bộ hóa, hai lần trước khi khởi tạo. Phải khai báo volatile cho instance để tránh lớp làm việc không chính xác do quá trình tối ưu hóa của trình biên dịch.
– Constructor của lớp Singleton được định nghĩa thành protected hoặc private để người dùng không thể tạo thực thể trực tiếp từ bên ngoài lớp.
– Phương thức getInstance() dùng để khởi tạo đối tượng duy nhất, định nghĩa thành public và static. Client chỉ dùng getInstance() để tạo đối tượng cho lớp Singleton.
– Thực hiện khởi tạo chậm (lazy initialization) trong getInstance(): chỉ khi gọi phương thức getInstance() mới khởi tạo đối tượng. Phương thức này trả về một thể hiện mới hay null tùy thuộc vào một tham số kiểu boolean dùng như cờ hiệu bào xem lớp Singleton đã tạo thể hiện hay chưa.
Trong chế độ multithreading, mẫu thiết kế Singleton có thể làm việc không tốt: do getInstance() không an toàn thread, hai thread có thể gọi phương thức sinh đối tượng cùng một thời điểm và hai thể hiện sẽ được tạo ra. Nếu đồng bộ (synchronized) phương thức getInstance() để an toàn thread sẽ dẫn đến giảm hiệu suất chương trình.
Có nhiều giải pháp cho vấn đề này: double-checked locking, enum, class loader (static block initialization).
– double-checked locking: kiểm tra sự tồn tại thể hiện của lớp, với sự hổ trợ của đồng bộ hóa, hai lần trước khi khởi tạo. Phải khai báo volatile cho instance để tránh lớp làm việc không chính xác do quá trình tối ưu hóa của trình biên dịch.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| package demosingleton; class Singleton { private static volatile Singleton instance; private Singleton(){} public static Singleton getInstance() { if (instance == null ) { synchronized (Singleton. class ) { if (instance == null ) { instance = new Singleton(); } } } return instance; } } public class DoubleCheckLockingSingleton { public static void main(String[] args) { System.out.println( "--- Singleton Pattern ---" ); Singleton single1 = Singleton.getInstance(); Singleton single2 = Singleton.getInstance(); if (single1.equals(single2)) { System.out.println( "Unique Instance" ); } } } |
-class loader (Java):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| package classloadersingleton; class Singleton { private static class Instance { static final Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return Instance.instance; } } public class ClassLoaderSingleton { public static void main(String[] args) { System.out.println( "--- Singleton Pattern ---" ); Singleton single1 = Singleton.getInstance(); Singleton single2 = Singleton.getInstance(); if (single1.equals(single2)) { System.out.println( "Unique Instance" ); } } } |
2. Liên quan:
– Abstract Factory: thường là Singleton để trả về các đối tượng factory duy nhất
– Builder: dùng để xây dựng một đối tượng phức tạp, trong đó Singleton được dùng để tạo một đối tượng truy cập tổng quát (Director).
– Prototype: dùng để sao chép một đối tượng, hoặc tạo ra một đối tượng khác từ Prototype của nó, trong đó Singleton được dùng để chắc chắn chỉ có một Prototype.
3. Java API
Lớp java.lang.Runtime là lớp Singleton, để lấy được đối tượng duy nhất của nó, ta gọi phương thức getRuntime(). Tương tự, lớp java.awt.Desktop cũng là lớp Singleton, tạo đối tượng duy nhất bằng phương thức getDesktop(). Singleton không phổ biến như ta nghỉ, nó chỉ áp dụng với lớp cần bảo đảm chỉ có một thể hiện duy nhất.
4. Sử dụng
Ta muốn:
- Đảm bảo rằng chỉ có một thể hiện của lớp.
- Quản lý việc truy cập tốt hơn vì chỉ có một thể hiện duy nhất.
- Quản lý số lượng thể hiện của một lớp. Trường hợp này không nhất thiết chỉ có một thể hiện, bạn cần kiểm soát số lượng thể hiện trong giớn hạn chỉ định
Vài phút hiểu về Java Singleton Pattern
Java Singleton Pattern là gì thế? Liệu rằng vài phút có hiểu hết được Java Singleton Pattern? Nó có nhiều lắm không? Những câu hỏi này mình sẽ giải đáp cho các bạn khi đọc hết những nội dung bên dưới nhé :)))

Trả lời cho câu hỏi Java Singleton Pattern là gì?
Singleton Pattern là một mẫu thiết kế (design pattern) có tên là singleton, thuộc nhóm Creational Design Pattern(nhóm này có 5 design pattern, singleton là 1 trong số 5 pattern đó).
Ngoài lề một tý trước khi hiểu tiếp về Singleton Pattern
Khi bạn định nghĩa một class (chẳng hạn như SinhVien), tại nhiều class khác (có thể là class SinhVienDemo1, SinhVienDemo2, QuanLySinhVien) bạn gọi đối tượng từ class SinhVien đó để sử dụng.
sv1, sv2, sv3 là 3 đối tượng thuộc class SinhVien, 3 đối tượng này là 3 thể hiện (instance) của class đó (đối tượng là thể hiện của class) và được gọi từ các class khác nhau. Các thể hiện này sẽ được trỏ đến các vùng nhớ khác nhau trong java virtual machine, đồng nghĩa với việc dữ liệu của 3 đối tượng sẽ khác nhau, không liên quan với nhau.
Nhưng đời đâu như là mơ, sếp mình lại bảo muốn có 3 đối tượng (3 tên đối tượng sv1, sv2, sv3), nhưng lại cùng trỏ đến một vùng nhớ trong java virtual machine thôi. Như vậy, chẳng khác gì cho 3 chàng trai cùng làm chồng của 1 cô gái (em ấy sướng quá rồi
).
Sao sếp lại làm chuyện ngược đời thế? Phải chăng chương trình chỉ quản lý 1 sinh viên duy nhất (lớp này ế nhỉ)
Nghe lời sếp, thế thì có giải pháp thế này:
Ở class SinhVienDemo1 bạn tạo ra đối tượng static sv1 với phạm vi truy cập public
Ở class SinhVienDemo2, QuanLySinhVien bạn chỉ cần tạo sv2, sv3 rồi nhận giá trị thôi
Hơi xàm rồi đấy, có nhiều cách để bạn thực hiện việc này, ở trên là 1 ví dụ. Tuy nhiên, với những cách thế này thì nhiều lúc sv2, sv3 bị dính NULL đó nhé, rồi tự nhiên muốn sử dụng SinhVien lại phải dùng SinhVienDemo1 làm class trung gian. Ôi! Cuộc đời sao bế tắc thế, ở nhà làm nông, chăn trâu chăn gà còn sướng hơn. 
Được anh sếp giới thiệu giải pháp Singleton Pattern
Chưa cần biết cách triển khai Singleton nó thế nào, cứ xem ví dụ đã nhé.
Ở class SinhVien bạn tạo một phương thức static svduynhat() trả về đối tượng của chính class SinhVien (phương thức này có phạm vi truy cập là public)
Rồi thì ở các class SinhVienDemo1, SinhVienDemo2, QuanLySinhVien chỉ cần tạo các đối tượng sv1, sv2, sv3 thế này
Như vậy, khi dùng Singleton Pattern mà sếp hướng dẫn, bạn đã thấy sung sướng hơn chưa, từ bỏ trâu gà quay lại nghiệp Code nhé 
[notification type=”alert-info” close=”false” ]
Lưu ý
Ví dụ trên chỉ code đơn giản theo hướng giải quyết của singleton pattern chứ chưa thật sự đầy đủ, bạn theo dõi những gì bên dưới rồi quay lại hoàn thành ví dụ nhé 
Vả lại, Mình chưa thấy một ứng dụng thực tế nào như ví dụ trên cả, tất cả chỉ là ví dụ để bạn hiểu singleton nó được áp dụng thế nào thôi 
[/notification]
Tiếp tục bàn về khái niệm Singleton Pattern
Từ ví dụ đơn giản trên, bạn có thể thấy Singleton Pattern giải quyết ổn thoả việc tạo ra một đối tượng (thể hiện) duy nhất từ một lớp để sử dụng trong suốt quá trình chương trình chạy.
Singleton Pattern nhiều lắm không?
Không rõ ở đây nhiều lắm là nhiều về cái gì? Tuy nhiên mình vẫn trả lời thế này.
Về mặt chức năng: Singleton đảm bảo rằng class chỉ có duy nhất một thể hiện (hay đối tượng) được tạo ra và nó sẽ cung cấp một phương thức để bạn truy cập đến thể hiện đó.
Về mặt nguyên tắc: Dù bạn thực hiện bằng cách nào đi chăng nữa thì cũng phải dựa vào các nguyên tắc sau:
- Constructor của class dùng Singleton Pattern phải có phạm vi truy cập là private để không thể tạo thể hiện của class từ bên ngoài.
- Khai báo private static cho tên biến của thể hiện đã tạo để đảm bảo thể hiện chỉ được tạo ra trong class đó thôi.
- Có một phương thức public để trả về thể hiện đã được tạo.
Như vậy chỉ cần nắm rõ chức năng và 3 nguyên tắc của singleton pattern là bạn có thể áp dụng vào project của mình, cách đặt tên thể hiện hay phương thức thì tuỳ bạn thôi. Vậy bạn thấy có nhiều không?
Đến đây thì thật sự không nhiều kiến thức về singleton pattern để bạn nhớ đâu, chỉ cần nhớ những điều trên là được. Tuy nhiên, bạn có đảm bảo rằng cách code của bạn theo nguyên lý singleton pattern thật sự hiệu quả và thực thi nhanh chóng. Không thể dám chắc đúng không? Vì bạn có thể không phải là một chuyên gia. 
Cách chuyên gia code theo nguyên lý singleton pattern
Đã gọi là chuyên gia thì ắt hẳn phải là một bậc thầy phải không các bạn. Vậy những cách áp dụng singleton của các bậc thầy như thế nào thì cùng mình điểm qua vài phương pháp chính sau nhé.
1. Eager initialization
Thể hiện của lớp sẽ được tạo ngay khi được gọi đến. Đây là cách dễ nhất nhưng nó có một nhược điểm là mặc dù thể hiện (instance) đã được khởi tạo nhưng có thể sẽ không dùng tới.
2. Static block initialization
Cũng tương tự Eager initialization nhưng có thêm phần static block cung cấp thêm tuỳ chọn để xử lý việc khác (chẳng hạn như lỗi).
3. Lazy Initialization
Là một cách làm mang tính mở rộng hơn so với 2 cách làm trên, hoạt động tốt trong từng thread đơn lẻ. Và tất nhiên vấn đề xấu sẽ xảy ra nếu chúng ta đang dùng nó với multi thread. (Để khắc phục nhược điểm này, làm theo cách thứ 4).
4. Thread Safe Singleton
Sử dụng thêm từ khóa synchronized trong phương thức trả về thể hiện của lớp. (Dùng trong các chương trình multi thread)
5. Bill Pugh Singleton Implementation
Với cách làm này sẽ tạo ra
static nested class
để tạo thể hiện (Cách mình hay dùng)6. Using Reflection to destroy Singleton Pattern
Reflection được dùng để destroy tất cả các singleton mà chúng ta đã tạo ra.
7. Enum Singleton
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. (Tuy nhiên mình không tin cho lắm
)
8. Serialization and Singleton
Serialization
là kỹ thuật sắp xếp đối tượng cần lưu trữ một cách tuần tự. Ví dụ bên dưới là quá trình đọc ghi dữ liệu tích hợp Singleton.
Còn đây là bảng đo hiệu suất khi sử dụng các phương pháp này

Có thể thấy cách làm khi sử dụng
inner-class static
(hay Bill Pugh Singleton Implementation) đạt hiệu suất cao nhất.
Như vậy, có nhiều chuyên gia, có nhiều cách thực hiện. Có cách tốt, cách tệ, cách có hiệu suất cao, cách có hiệu suất thấp, cách dùng tốt với single thread, cách dùng tốt với multi thread,…nên bạn phải nhớ rằng bản chất mới là quan trọng, cách thức thực hiện chẳng là gì cả. Ở một số dự án bạn thực hiện theo phương pháp này, nhưng một số dự án bạn bắt buộc phải thực hiện theo một phương pháp khác. Các phương pháp trên của các chuyên gia đã được kiểm chứng và sử dụng trong nhiều dự án, bạn có thể sử dụng một trong những phương pháp đó nhé. (Có thể chọn cách Bill Pugh Singleton Implementation vì bạn cũng thấy hiệu suất của nó rồi đó
)
Ứng dụng của Singleton Pattern
Bạn thử hình dung trong đầu mình rằng đã hiểu về Singleton Pattern như thế nào rồi nhé. Cứ việc tập trung vào chức năng của nó: “đảm bảo rằng class chỉ có duy nhất một thể hiện (hay đối tượng) được tạo ra và nó sẽ cung cấp một phương thức để bạn truy cập đến thể hiện đó” để có thể ứng dụng vào trong các dự án mà bạn cần đến.
Dưới đây là một số ứng dụng của singleton pattern để bạn tham khảo thêm:
- Vì class dùng Singleton chỉ tồn tại 1 Instance (thể hiện) 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 (logging), Configuration class, DAO, drivers objects, caching hoặc thread pool.
- Một số design pattern khác cũng sử dụng Singleton để triển khai: Abstract Factory, Builder, Prototype, Facade,…
- Singleton Pattern cũng được sử dụng trong một số class của core java, ví dụ như: java.lang.Runtime, java.awt.Desktop.
Sếp nói thêm
À thật ra thì không phải sếp nói là mình tóm lại thôi nhé. 
Đó là tất cả những gì mình hiểu, tìm hiểu và vận dụng Singleton Pattern. Mình khuyên các bạn chỉ cần hiểu đơn giản thôi, đừng quan trọng hoá vấn đề.
Phương pháp Singleton mình hay sử dụng là Bill Pugh Singleton Implementation (vì nó có hiệu suất cao), Lazy Initialization (vì tính đơn giản của nó nếu không dùng đến multi thread) và Thread Safe Singleton (tuy hiệu suất thấp nhưng là cách khá an toàn khi có dùng multi thread). Code ví dụ thì đã có ở trên, các bạn xem lại nhé.
Cuối cùng, hẹn gặp lại các bạn ở những bài viết phân tích sâu hơn về từng phương pháp của các chuyên gia để thấy ưu nhược điểm của từng cái và các ví dụ mẫu cho từng phương pháp đó.
Nếu có vấn đề gì cần thảo luận thì để lại bình luận bên dưới bạn nhé <3
Nhận xét
Đăng nhận xét