Singleton trong Java là một design pattern đảm bảo chỉ duy nhất một đối tượng của lớp được tồn tại trong suốt quá trình chạy của một chương trình. Pattern này được sử dụng chỉ khi bạn cần một phiên bản duy nhất của lớp trong toàn bộ quá trình chạy chương trình và đối tượng đó phải có khả năng truy cập từ bên ngoài các lớp khác. Trong bài viết này, Stringee sẽ cùng các bạn tìm hiểu về Singleton pattern trong Java.
1. Singleton pattern trong Java là gì
Singleton
là 1 trong 5 design pattern của nhóm Creational Design Pattern
.
Singleton đảm bảo chỉ duy nhất một thể hiện (instance) được tạo ra và nó sẽ cung cấp cho bạn một method để có thể truy xuất được thể hiện duy nhất đó mọi lúc mọi nơi trong chương trình. Trong bài viết này, Stringee sẽ cùng các bạn tìm hiểu về Singleton pattern trong Java nhé
2. Một số nguyên tắc về Singleton Pattern trong Java
Singleton hạn chế việc khởi tạo đối tượng từ lớp và đảm bảo chỉ có một đối tượng của lớp tồn tại trong máy ảo JVM
Lớp singleton phải cung cấp một điểm truy cập mở để có thể lấy ra đối tượng của lớp
Singleton pattern được sử dụng để ghi
log
, đối tượngdriver
, lưucache
và sử lýthreadpool
Singleton design pattern cũng được sử dụng trong các pattern khác như
Abstract Factory
,Builder
,Prototype
,Facade
, etc.Singleton design pattern được sử dụng trong các lớp core Java (ví dụ,
java.lang.Runtime
,java.awt.Desktop
)
3. Implementation của Singleton Pattern trong Java
Để triển khai singleton pattern, chúng ta có thể sử dụng nhiều cách, tuy nhiên các cách này đều có một số đặc điểm chung như sau:
Constructor
phải là private để hạn chế việc khởi tạo đối tượng từ các lớp khácCác biến
private static
trong cùng lớp và nó phải là đối tượng duy nhất trong lớpMột phương thức public
static
trả về đối tượng của lớp, đây là điểm truy cập để cho các lớp bên ngoài có thể lấy đối tượng từ lớp Singleton
Trong phần dưới đây, chúng ta sẽ tìm hiểu các cách khác nhau để thực hiện triển khai Singleton pattern và thiết kế các điều kiện cần phải để tâm với các cách triển khai khác nhau
3.1. Eager Initialization
Trong cách triển khai này, đối tượng của lớp Singleton được tạo ngay khi lớp đang được tải bởi JVM. Nhược điểm của cách triển khai này đó là đối tượng sẽ được khởi tạo kể cả khi mà client có thể không sử dụng đến nó.
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
// private constructor to avoid client applications using the constructor
private EagerInitializedSingleton(){}
public static EagerInitializedSingleton getInstance() {
return instance;
}
}
Nếu lớp Singleton
của bạn không sử dụng nhiều tài nguyên, cách cài đặt này là một cách tiếp cận nên được sử dụng. Tuy nhiên, trong đa số các trường hợp, Singleton được sử dụng cho các lớp như FileSystem
, kết nối tới Database
, ... Chúng ta nên tránh việc khởi tạo nếu như client không gọi đến phương thức getInstance
. Và cũng do cách khởi tạo ngay khi tải lớp nên cách tiếp cận này không cung cấp một cách xử lý exception
nào cả.
3.2. Static block initialization
Cách tiếp cận này tương tự với Eager Initialization
, thế nhưng đối tượng sẽ được khởi tạo trong static
block. Do đó, chúng ta có thể xử lý exception trong block này:
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 occurred in creating singleton instance");
}
}
public static StaticBlockSingleton getInstance() {
return instance;
}
}
Nhược điểm chung của cách tiếp cận này cũng giống như eager initialization đó là đối tượng được khởi tạo ngay cả khi client không cần sử dụng đến nó.
3.3. Lazy Initialization
Lazy initialization khởi tạo một đối tượng trong một phương thức cho phép truy cập từ bên ngoài. Dưới đây là ví dụ cho cách triển khai này:
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
private LazyInitializedSingleton(){}
public static LazyInitializedSingleton getInstance() {
if (instance == null) {
instance = new LazyInitializedSingleton();
}
return instance;
}
}
Cách tiếp cận này có thể hoạt động đúng đắn với môi trường đơn luồng, tuy nhiên, nếu bạn làm việc với môi trường đa luồng(đa số các chương trình production hiện nay), vấn đề có thể xảy ra tại điều kiện if
. Khi đó, pattern
sẽ bị sai do JVM sẽ tạo hai đối tượng khác nhau cho hai thread khác nhau đang cùng xét điều kiện if này. Trong các phần tiếp theo, chúng ta sẽ cùng tìm hiểu các cách khác nhau để tạo ra các lớp Singleton thread-safe
3.4. Thread Safe Singleton
Một cách đơn giản để tạo một lớp Singleton thread-safe
đó là chỉ định phương thức truy cập đối tượng là synchronized
, do đó chỉ một thread có thể thực thi phương thức này tại một thời điểm.
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton(){}
public static synchronized ThreadSafeSingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
}
Cách này có nhược điểm là một phương thức synchronized
sẽ chạy rất chậm và tốn hiệu năng, bất kỳ Thread nào gọi đến đều phải chờ nếu có một Thread khác đang sử dụng. Có những tác vụ xử lý trước và sau khi tạo thể hiện không cần thiết phải block
. Vì vậy chúng ta cần cải tiến nó đi 1 chút với Double Check Locking Singleton
.
public static ThreadSafeSingleton getInstanceUsingDoubleLocking() {
if (instance == null) {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
3.5. Bill Pugh Singleton Implementation
Với cách làm này bạn sẽ tạo ra static nested
class với vai trò 1 Helper khi muốn tách biệt chức năng cho 1 class function rõ ràng hơn. Đây là cách thường hay được sử dụng và có hiệu suất tốt .
public class BillPughSingleton {
private BillPughSingleton(){}
private static class SingletonHelper {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
3.6. Phá vỡ cấu trúc Singleton bằng Reflection
Cấu trúc Singleton có thể bị phá vỡ bằng cách sử dụng Reflection
import java.lang.reflect.Constructor;
public class ReflectionSingletonTest {
public static void main(String[] args) {
EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
EagerInitializedSingleton instanceTwo = null;
try {
Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
for (Constructor constructor : constructors) {
// This code will destroy the singleton pattern
constructor.setAccessible(true);
instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
break;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(instanceOne.hashCode());
System.out.println(instanceTwo.hashCode());
}
}
Output của chương trình:
2018699554
1311053135
Tương tự Eager Initialization
, implement theo Bill Pugh Singleton
cũng bị break bởi Reflection.
3.7. Enum Singleton
Để tránh việc cấu trúc bị phá vỡ bởi Reflection
, Josua Bloch khuyên chúng ta nên sử dụng enum để triển khai Singleton bởi vì Java đảm bảo việc Enum
sẽ chỉ được khởi tạo một lần duy nhất trong một chương trình Java. Vì các giá trị enum của Java có thể được truy cập từ bên ngoài Lớp của nó, vì vậy nó cũng là Singleton
. Nhược điểm là kiểu enum sẽ không linh động:
public enum EnumSingleton {
INSTANCE;
public static void doSomething() {
// implement what to do with this class
}
}
3.8. Serialization và Singleton
Đôi khi trong các hệ thống phân tán (distributed system), chúng ta cần implement interface Serializable
trong lớp Singleton
để chúng ta có thể lưu trữ trạng thái của nó trong file hệ thống và truy xuất lại nó sau.
import java.io.Serializable;
public class SerializedSingleton implements Serializable {
private static final long serialVersionUID = -7604766932017737115L;
private SerializedSingleton(){}
private static class SingletonHelper {
private static final SerializedSingleton instance = new SerializedSingleton();
}
public static SerializedSingleton getInstance() {
return SingletonHelper.instance;
}
}
Vấn đề ở đây là khi chúng ra serialize
một lớp Singleton thì khi chúng ta deserialize
nó, nó sẽ tạo một đối tượng mới của lớp
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class SingletonSerializedTest {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
SerializedSingleton instanceOne = SerializedSingleton.getInstance();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
out.writeObject(instanceOne);
out.close();
// deserialize from file to object
ObjectInput in = new ObjectInputStream(new FileInputStream("filename.ser"));
SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
in.close();
System.out.println("instanceOne hashCode="+instanceOne.hashCode());
System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
}
}
Output
instanceOne hashCode=2011117821
instanceTwo hashCode=109647522
Để xử lý trường hợp này, chúng ta cần implement phương thức readResolve()
protected Object readResolve() {
return getInstance();
}
Kết
Có rất nhiều cách implement cho Singleton, thông thường Bill Pugh Singleton hay được các lập trình viên sử dụng vì có hiệu suất cao, sử dụng LazyInitializedSingleton cho những ứng dụng chỉ làm việc với ứng dụng single-thread và sử dụng DoubleCheckLockingSingleton khi làm việc với ứng dụng multi-thread. Tùy theo trường hợp cụ thể, bạn hãy chọn cho mình cách implement phù hợp.
Stringee API cung cấp các tính năng như gọi thoại, gọi video, tin nhắn chat, SMS hay tổng đài chăm sóc khách hàng (CSKH) có thể được nhúng trực tiếp vào các ứng dụng/website của doanh nghiệp nhanh chóng. Điều này giúp tiết kiệm đến 80% thời gian và chi phí cho doanh nghiệp, trong khi nếu tự phát triển các tính năng này có thể mất từ 1 - 3 năm.
Quý doanh nghiệp quan tâm xin mời đăng ký nhận tư vấn tại đây: