Post

Singleton Pattern trong Java

Singleton Singleton Pattern là một trong những Design Patterns nổi bật trong nhóm Creational Pattern, giúp quản lý việc tạo đối tượng trong một ứng dụng.

Được sử dụng để đảm bảo rằng một lớp chỉ có duy nhất một instance (thể hiện) và cung cấp một điểm truy cập toàn cục đến instance đó, Singleton được áp dụng phổ biến trong các ứng dụng khi cần chia sẻ tài nguyên hoặc quản lý trạng thái chung.

1. Ưu điểm

  • Tiết kiệm tài nguyên: Singleton giúp hạn chế việc tạo nhiều đối tượng không cần thiết, từ đó tiết kiệm tài nguyên hệ thống.
  • Quản lý nhất quán: Với một instance duy nhất, trạng thái chung được quản lý dễ dàng và nhất quán.
  • Dễ triển khai và sử dụng: Cách triển khai đơn giản và rõ ràng giúp lập trình viên dễ dàng tiếp cận và tích hợp.

2. Nhược điểm

  • Khó khăn trong việc mở rộng: Việc mở rộng lớp Singleton có thể gặp khó khăn do ràng buộc instance duy nhất.
  • Khả năng gây ra vấn đề trong môi trường đa luồng: Singleton không được triển khai đúng cách có thể dẫn đến race condition và các lỗi liên quan đến concurrency.
  • Phụ thuộc vào trạng thái toàn cục: Nếu không được quản lý cẩn thận, Singleton có thể dẫn đến việc chia sẻ trạng thái không mong muốn, gây khó khăn trong việc debug và bảo trì.

3. Implement

Singleton Pattern thường được áp dụng trong các tình huống như:

  • Quản lý kết nối cơ sở dữ liệu: Đảm bảo rằng chỉ có một kết nối duy nhất đến cơ sở dữ liệu tại một thời điểm.
  • Cấu hình ứng dụng: Lưu trữ các thông tin cấu hình chung mà nhiều thành phần khác nhau của ứng dụng cần truy cập.
  • Logging: Duy trì một logger duy nhất để ghi lại các thông tin log trong ứng dụng.
  • Cơ chế cache: Quản lý cache tập trung để tối ưu hóa việc truy xuất dữ liệu.

Có nhiều cách để triển khai Singleton Pattern trong Java, từ cách đơn giản nhất đến những cách tối ưu hóa cao cấp hơn để hỗ trợ môi trường đa luồng.

Nhưng dù cho việc implement bằng cách nào đi nữa cũng dựa vào nguyên tắc dưới đây cơ bản dưới đây:

  • private constructor để hạn chế truy cập từ class bên ngoài.
  • Đặt private static final variable đảm bảo biến chỉ được khởi tạo trong class.
  • Có một method public static để return instance được khởi tạo ở trên.

Dưới đây là một số cách triển khai phổ biến.

3.1 Singleton cơ bản

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BasicSingleton {
    private static BasicSingleton instance;

    private BasicSingleton() {
        // Ngăn không cho khởi tạo từ bên ngoài
    }

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

Cách triển khai này đơn giản, dễ hiểu nhưng không an toàn trong môi trường đa luồng.

Nếu hai luồng gọi getInstance() cùng lúc, có thể dẫn đến việc tạo ra hai instance.

3.2 Singleton với từ khóa synchronized

1
2
3
4
5
6
7
8
9
10
11
12
public class ThreadSafeSingleton {
    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {}

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

Tuy cách này đảm bảo an toàn trong môi trường đa luồng. Tuy nhiên sử dụng từ khóa synchronized khiến hiệu suất giảm trong trường hợp getInstance() được gọi thường xuyên.

3.3 Double-checked locking

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DCLSingleton {
    private static volatile DCLSingleton instance;

    private DCLSingleton() {}

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

Nhận xét đây là cách triển khai tối ưu, tránh được việc đồng bộ hóa liên tục, nâng cao hiệu suất. Nhược điểm duy nhất là pức tạp hơn so với các cách khác.

3.4 Singleton sử dụng Enum

1
2
3
4
5
6
7
public enum EnumSingleton {
    INSTANCE;

    public void showMessage() {
        System.out.println("Hello from Singleton!");
    }
}

Cách tiếp cận này là an toàn nhất, đảm bảo chống lại việc phá vỡ Singleton trong Java nhờ đặc tính của Enum. Nhưng không linh hoạt nếu cần khởi tạo Singleton với tham số.

4. Ví dụ bonus thêm

Giả sử bạn cần một Logger duy nhất trong toàn bộ ứng dụng:

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
public class Logger {
    private static Logger instance;

    private Logger() {}

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

    public void log(String message) {
        System.out.println("[LOG] " + message);
    }
}

// Cách sử dụng
public class MainApp {
    public static void main(String[] args) {
        Logger logger = Logger.getInstance();
        logger.log("Application started");
    }
}

Lời kết

Singleton Pattern là một trong những pattern đơn giản nhưng hiệu quả, thường xuyên xuất hiện trong các dự án thực tế.

Khi triển khai Singleton, cần cân nhắc đến môi trường đa luồng và hiệu suất của ứng dụng để chọn phương pháp phù hợp nhất.

Nhờ vào việc quản lý tài nguyên tốt và cung cấp tính nhất quán, Singleton vẫn luôn là một lựa chọn đáng cân nhắc trong các tình huống sử dụng cụ thể.

Bài viết mang tính chất “ghi chú, lưu trữ, chia sẻ và phi lợi nhuận”.
Nếu bạn thấy hữu ích, đừng quên chia sẻ với bạn bè và đồng nghiệp của mình nhé!

Happy coding! 😎 👍🏻 🚀 🔥

This post is licensed under CC BY 4.0 by the author.