Singleton an toàn với luồng
Thuộc phần Lập trình hướng đối tượng trong hành trình C# của Coddy. Bài học 52 trên 70.
Mẫu Singleton đảm bảo một lớp chỉ có một instance trong toàn bộ ứng dụng và cung cấp một điểm truy cập toàn cục đến instance đó. Mẫu này hữu ích khi quản lý các tài nguyên dùng chung như cài đặt cấu hình, dịch vụ ghi nhật ký hoặc kết nối cơ sở dữ liệu.
Singleton cơ bản dùng hàm khởi tạo riêng tư để ngăn việc tạo đối tượng từ bên ngoài và dùng thuộc tính tĩnh để cung cấp quyền truy cập:
public class Logger
{
private static Logger _instance;
private static readonly object _lock = new object();
private Logger() { } // Hàm khởi tạo (constructor) riêng tư
public static Logger Instance
{
get
{
lock (_lock)
{
if (_instance == null)
_instance = new Logger();
return _instance;
}
}
}
public void Log(string message) => Console.WriteLine(message);
}Câu lệnh lock đảm bảo an toàn luồng. Nếu nhiều luồng cùng lúc cố truy cập Instance, mỗi lần chỉ một luồng có thể vào vùng quan trọng. Nếu không có điều này, hai luồng có thể cùng thấy _instance là null và tạo các đối tượng riêng biệt.
C# cung cấp một cách tiếp cận an toàn với luồng đơn giản hơn bằng cách khởi tạo tĩnh:
public class Logger
{
private static readonly Logger _instance = new Logger();
private Logger() { }
public static Logger Instance => _instance;
public void Log(string message) => Console.WriteLine(message);
}Phiên bản này an toàn với luồng vì CLR đảm bảo việc khởi tạo trường static chỉ diễn ra một lần. Cả hai cách đều đảm bảo bạn luôn nhận được cùng một đối tượng:
Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");Thử thách
DễHãy xây dựng một trình quản lý cấu hình an toàn với luồng bằng mẫu Singleton. Trong các ứng dụng thực tế, bạn cần truy cập các thiết lập cấu hình ở bất cứ đâu trong chương trình, đồng thời đảm bảo chỉ có một instance tồn tại. Đây là trường hợp sử dụng hoàn hảo cho Singleton.
Bạn sẽ sắp xếp code trong hai tệp:
ConfigManager.cs: Tạo lớpConfigManagertrong namespaceConfigurationđể triển khai mẫu Singleton. Trình quản lý cấu hình cần:- Có một trường static riêng tư để lưu instance duy nhất
- Dùng hàm khởi tạo riêng tư để ngăn việc khởi tạo từ bên ngoài
- Cung cấp thuộc tính static
Instancetrả về instance duy nhất, dùng cách khởi tạo static đơn giản hơn để đảm bảo an toàn với luồng - Có một trường chuỗi riêng tư
_appNameđược khởi tạo với giá trị"MyApp" - Có phương thức
SetAppName(string name)để cập nhật tên ứng dụng - Có phương thức
GetAppName()trả về tên ứng dụng hiện tại
Program.cs: Trong tệp chính, hãy chứng minh Singleton hoạt động chính xác bằng cách truy cập instance nhiều lần và cho thấy các thay đổi được giữ lại trong tất cả các tham chiếu.
Bạn sẽ nhận một đầu vào:
- Tên ứng dụng mới cần đặt, ví dụ:
ProductionServer
Trong chương trình:
- Lấy instance
ConfigManagervà in tên ứng dụng mặc định - Lấy lại instance, lưu vào một biến khác, rồi dùng nó để đặt tên ứng dụng mới từ đầu vào
- Lấy instance lần thứ ba và in tên ứng dụng để chứng minh tất cả tham chiếu đều dùng chung dữ liệu
Ví dụ, nếu đầu vào là ProductionServer, đầu ra sẽ là:
MyApp
ProductionServerĐiều này minh họa lợi ích cốt lõi của Singleton: dù bạn truy cập Instance bao nhiêu lần, bạn luôn nhận được cùng một đối tượng với trạng thái dùng chung. Cách khởi tạo static đảm bảo an toàn với luồng vì CLR đảm bảo trường static chỉ được khởi tạo một lần, ngay cả khi nhiều luồng truy cập đồng thời.
Tự làm thử
using System;
using Configuration;
class Program
{
public static void Main(string[] args)
{
// Đọc đầu vào: tên ứng dụng mới
string newAppName = Console.ReadLine();
// TODO: Lấy instance ConfigManager và in ra tên ứng dụng mặc định
// TODO: Lấy lại instance (lưu vào một biến khác) và dùng nó để đặt tên ứng dụng mới
// TODO: Lấy instance lần thứ ba và in ra tên ứng dụng để chứng minh mọi tham chiếu đều dùng chung dữ liệu
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Các khái niệm cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiKhông gian tên và chỉ thịGiới thiệu về lớp và đối tượngTừ khóa thisPhương thức và tham sốTrường và thuộc tínhHàm khởi tạoKhởi tạo đối tượngÔn tập: Máy tính đơn giản4Kế thừa
Cú pháp kế thừa cơ bản (:)Từ khóa 'base'Từ khóa virtual và overrideLớp sealedLớp cơ sở 'object'Ôn tập: hệ phân cấp nhân viên7Tính năng nâng cao
Nạp chồng toán tửBộ lập chỉ mục (this[])Ghi đè ToString()Phương thức mở rộngÔn tập: Danh sách tùy chỉnh10Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếSingleton an toàn với luồngMẫu FactoryMẫu Observer (sự kiện)Mẫu Strategy2Thuộc tính và thành viên tĩnh
Thuộc tính tự độngThuộc tính chỉ đọc hoặc chỉ ghiTrường và phương thức tĩnhLớp tĩnhThành viên có thân biểu thức5Đa hình và interface
Đa hình lúc biên dịch và lúc chạyInterface và lớp trừu tượngNhiều interfaceInterface tường minhUpcasting và downcastingÔn tập: bộ tính toán hình học8Các khái niệm OOP nâng cao
Ưu tiên composition hơn kế thừaGenerics (lớp và phương thức)Delegate và sự kiệnAttribute và reflectionGiao diện IDisposable và câu lệnh usingKiến thức cơ bản về dependency injection11Các mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổ hợpTự luyện tập: Trình biên dịch C# online