Menu
CoddyTech

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");
challenge icon

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ớp ConfigManager trong namespace Configuration để 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 Instance trả 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:

  1. Lấy instance ConfigManager và in tên ứng dụng mặc định
  2. 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
  3. 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
    }
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch C# online