Trang chủHướng dẫnCách triển khai Thread Safe Singleton trong Java
Java

Cách triển khai Thread Safe Singleton trong Java

CyStack blog 3 phút để đọc
CyStack blog03/07/2025
Locker Avatar

Ngoc Vo

Marketing Executive @CyStack
Locker logo social
Reading Time: 3 minutes

Singleton là một trong những design pattern dạng khởi tạo (creational) được sử dụng rộng rãi nhất. Mục đích của nó là hạn chế số lượng object được tạo ra bởi ứng dụng. Khi sử dụng Singleton trong môi trường đa luồng, việc đảm bảo an toàn luồng cho lớp Singleton (Thread Safe Singleton) trở nên cực kỳ quan trọng.

thread safe singleton trong java

Trong các ứng dụng thực tế, những tài nguyên như kết nối đến cơ sở dữ liệu hay hệ thống thông tin doanh nghiệp (Enterprise Information Systems, EIS) thường có giới hạn và cần được sử dụng một cách khôn ngoan để tránh tình trạng cạn kiệt.

Để giải quyết vấn đề này, ta có thể áp dụng mẫu thiết kế Singleton. Chúng ta sẽ tạo một lớp bọc (wrapper class) cho tài nguyên và giới hạn số lượng object được tạo ra tại runtime chỉ còn một để đảm bảo an toàn luồng khi triển khai Singleton trong Java.

Cách triển khai Thread Safe Singleton trong Java

Nhìn chung, để tạo một lớp singleton, ta thường thực hiện các bước sau:

  1. Tạo constructor (hàm khởi tạo) dạng private để ngăn việc tạo object mới bằng toán tử new.
  2. Khai báo một thực thể dạng private static của chính lớp đó.
  3. Cung cấp một phương thức dạng public static để trả về thực thể của lớp singleton. Nếu biến thực thể chưa được khởi tạo, phương thức này sẽ khởi tạo nó. Nếu không, nó sẽ trả về thực thể đã có.

Đoạn code dưới đây sử dụng các nguyên tắc trên.

package com.journaldev.designpatterns;

public class ASingleton {

	private static ASingleton instance = null;

	private ASingleton() {
	}

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

}

Ở đây, phương thức getInstance() không có tính an toàn luồng. Nhiều thread có thể truy cập nó cùng một lúc. Khi biến instance chưa được khởi tạo, các thread đầu tiên có thể cùng đi vào khối lệnh if và tạo ra nhiều thực thể khác nhau. Điều này làm phá vỡ mục đích của việc triển khai Singleton.

Những cách tạo an toàn luồng cho Singleton trong Java

Có ba cách để chúng ta có thể đảm bảo điều này.

  1. Tạo biến instance ngay tại thời điểm class được load

Ưu điểm:

  • Đảm bảo an toàn luồng mà không cần đồng bộ.
  • Dễ triển khai.

Nhược điểm:

  • Tài nguyên được tạo sớm, có thể không bao giờ được dùng đến trong ứng dụng.
  • Ứng dụng client không thể truyền tham số, do đó ta không thể tái sử dụng nó. Ví dụ, ta không thể có một lớp singleton dùng chung cho việc kết nối database, khi mà ứng dụng client cần cung cấp các thuộc tính của database server.
  1. Đồng bộ hóa cho phương thức getInstance()

Ưu điểm:

  • Đảm bảo an toàn luồng.
  • Ứng dụng client có thể truyền tham số.
  • Đạt được lazy initialization (khởi tạo trễ).

Nhược điểm:

  • Hiệu năng chậm do gánh nặng xử lý từ cơ chế locking (khóa).
  • Việc đồng bộ hóa trở nên không cần thiết sau khi biến thực thể đã được khởi tạo.
  1. Sử dụng khối đồng bộ bên trong khối lệnh if và biến volatile

Ưu điểm:

  • Đảm bảo an toàn luồng.
  • Ứng dụng client có thể truyền tham số.
  • Đạt được lazy initialization.
  • Giảm tối đa gánh nặng xử lý của việc đồng bộ hóa và chỉ áp dụng cho vài thread đầu tiên khi biến còn null.

Nhược điểm:

  • Tốn thêm một câu lệnh if.

Sau khi xem xét cả ba phương pháp trên, có thể thấy cách thứ ba là lựa chọn tối ưu nhất. Class đã chỉnh sửa sẽ trông như sau:

package com.journaldev.designpatterns;

public class ASingleton {

	private static volatile ASingleton instance;
	private static Object mutex = new Object();

	private ASingleton() {
	}

	public static ASingleton getInstance() {
		ASingleton result = instance;
		if (result == null) {
			synchronized (mutex) {
				result = instance;
				if (result == null)
					instance = result = new ASingleton();
			}
		}
		return result;
	}

}

Biến cục bộ result có vẻ không cần thiết ở đây, nhưng nó được dùng để cải thiện hiệu năng của code. Trong hầu hết các trường hợp, khi thực thể đã được khởi tạo, trường volatile chỉ bị truy cập một lần duy nhất (nhờ câu lệnh return result; thay vì return instance;).

Kỹ thuật này có thể cải thiện hiệu năng chung của phương thức lên đến 25%. Nếu bạn tin rằng có giải pháp nào tốt hơn, hoặc việc triển khai trên có thể ảnh hưởng đến tính an toàn luồng, hãy chia sẻ với chúng tôi biết bằng cách bình luận bên dưới.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất