Mẫu C # Singleton An toàn Chủ đề


79

Tôi có một số câu hỏi liên quan đến mẫu singleton như được ghi lại tại đây: http://msdn.microsoft.com/en-us/library/ff650316.aspx

Đoạn mã sau là một đoạn trích từ bài báo:

using System;

public sealed class Singleton
{
   private static volatile Singleton instance;
   private static object syncRoot = new object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         if (instance == null) 
         {
            lock (syncRoot) 
            {
               if (instance == null) 
                  instance = new Singleton();
            }
         }

         return instance;
      }
   }
}

Cụ thể, trong ví dụ trên, có cần phải so sánh instance với null hai lần, trước và sau khóa không? Điều này có cần thiết không? Tại sao không thực hiện khóa trước và thực hiện so sánh?

Có vấn đề gì trong việc đơn giản hóa những điều sau không?

   public static Singleton Instance
   {
      get 
      {
        lock (syncRoot) 
        {
           if (instance == null) 
              instance = new Singleton();
        }

         return instance;
      }
   }

Việc thực hiện khóa có đắt không?


17
Là một sang một bên, Jon Skeet có một bài viết xuất sắc về chủ đề an toàn trong Singletons: csharpindepth.com/Articles/General/Singleton.aspx
Arran

lười biếng tĩnh init sẽ thích hợp hơn ...
Mitch Wheat

1
Tôi cũng có những ví dụ khác với lời giải thích ở đây: csharpindepth.com/Articles/General/Singleton.aspx
Serge Voloshenko

Câu hỏi chính xác ở đây cho thế giới Java.
RBT

Câu trả lời:


133

Việc thực hiện khóa tốn kém kinh khủng khi so sánh với việc kiểm tra con trỏ đơn giản instance != null.

Kiểu bạn thấy ở đây được gọi là khóa được kiểm tra hai lần . Mục đích của nó là để tránh thao tác khóa tốn kém chỉ cần thiết một lần (khi singleton được truy cập lần đầu). Việc triển khai là như vậy bởi vì nó cũng phải đảm bảo rằng khi singleton được khởi tạo sẽ không có lỗi do các điều kiện chạy luồng.

Hãy nghĩ về nó theo cách này: một nullkiểm tra trần (không có a lock) được đảm bảo cung cấp cho bạn một câu trả lời có thể sử dụng chính xác chỉ khi câu trả lời đó là "có, đối tượng đã được xây dựng". Nhưng nếu câu trả lời là "chưa được xây dựng" thì bạn không có đủ thông tin vì điều bạn thực sự muốn biết là nó "chưa được xây dựng và không có luồng nào khác có ý định xây dựng nó trong thời gian ngắn ". Vì vậy, bạn sử dụng kiểm tra bên ngoài như một kiểm tra ban đầu rất nhanh và bạn bắt đầu quy trình thích hợp, không có lỗi nhưng "tốn kém" (khóa rồi kiểm tra) chỉ khi câu trả lời là "không".

Việc triển khai ở trên là đủ tốt cho hầu hết các trường hợp, nhưng tại thời điểm này, bạn nên đọc bài viết của Jon Skeet về singleton trong C # , bài viết cũng đánh giá các lựa chọn thay thế khác.


1
Cảm ơn bạn đã phản hồi đầy đủ thông tin với các liên kết hữu ích. Nhiều đánh giá cao.
Wayne Phipps

Liên kết khóa - được kiểm tra hai lần không hoạt động nữa.
El Mac

Tôi xin lỗi, ý tôi là người khác.
El Mac

1
@ElMac: Trang web của Skeet đang ngừng hoạt động ATM, nó sẽ được sao lưu trong thời gian thích hợp. Tôi sẽ ghi nhớ nó và đảm bảo rằng liên kết vẫn hoạt động khi nó xuất hiện, cảm ơn.
Jon

3
Kể từ .NET 4.0 Lazy<T>thực hiện công việc này một cách hoàn hảo.
ilyabreev

34

Các Lazy<T>phiên bản:

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy
        = new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance
        => lazy.Value;

    private Singleton() { }
}

Yêu cầu .NET 4 và C # 6.0 (VS2015) hoặc mới hơn.


Tôi nhận được "System.MissingMemberException: 'Các loại lười biếng-khởi tạo không có một cộng đồng, xây dựng parameterless'" Với mã này trên Net 4.6.1 / C # 6.
ttugates

@ttugates, bạn nói đúng, cảm ơn. Đã cập nhật mã với một lệnh gọi lại giá trị của nhà máy cho đối tượng lười biếng.
andasa

14

Thực hiện khóa: Khá rẻ (vẫn đắt hơn kiểm tra bằng rỗng).

Thực hiện khóa khi một chuỗi khác có nó: Bạn nhận được chi phí của bất cứ điều gì họ vẫn phải làm trong khi khóa, cộng vào thời gian của riêng bạn.

Thực hiện khóa khi một luồng khác có nó, và hàng chục luồng khác cũng đang chờ nó: Crippling.

Vì lý do hiệu suất, bạn luôn muốn có các khóa mà một luồng khác muốn, trong một khoảng thời gian ngắn nhất có thể.

Tất nhiên, lý luận về ổ khóa "rộng" dễ dàng hơn là hẹp, vì vậy bạn nên bắt đầu với chúng rộng và tối ưu hóa khi cần thiết, nhưng có một số trường hợp chúng tôi rút kinh nghiệm và quen thuộc khi khóa hẹp hơn phù hợp với mô hình.

(Ngẫu nhiên, nếu bạn có thể chỉ sử dụng private static volatile Singleton instance = new Singleton()hoặc nếu bạn có thể không sử dụng singleton mà thay vào đó sử dụng một lớp tĩnh, cả hai đều tốt hơn về những mối quan tâm này).


1
Tôi thực sự thích suy nghĩ của bạn ở đây. Đó là một cách tuyệt vời để xem xét nó. Tôi ước gì có thể chấp nhận hai câu trả lời hoặc 5 thế này, cảm ơn nhiều
Wayne Phipps

2
Một hệ quả mà trở nên quan trọng khi nó là thời gian để nhìn vào hiệu suất, là sự khác biệt giữa cấu trúc chia sẻ rằng có thể được nhấn đồng thời và những người sẽ . Đôi khi chúng ta không mong đợi hành vi như vậy xảy ra thường xuyên, nhưng nó có thể xảy ra, vì vậy chúng ta cần phải khóa (chỉ mất một lần khóa là có thể phá hỏng mọi thứ). Lần khác, chúng ta biết rằng rất nhiều chủ đề thực sự sẽ đánh cùng một đối tượng đồng thời. Tuy nhiên, những lần khác, chúng tôi đã không mong đợi sẽ có nhiều sự tương đồng, nhưng chúng tôi đã nhầm. Khi bạn cần cải thiện hiệu suất, những thứ có nhiều đồng thời sẽ được ưu tiên.
Jon Hanna

volatileTuy nhiên, thay thế của bạn là không cần thiết readonly. Xem stackoverflow.com/q/12159698/428724 .
wezten

7

Lý do là hiệu suất. Nếu instance != null(sẽ luôn luôn như vậy ngoại trừ lần đầu tiên), không cần phải làm gì tốn kém lock: Hai luồng truy cập đồng thời singleton đã khởi tạo sẽ được đồng bộ hóa không liên tục.


4

Trong hầu hết mọi trường hợp (nghĩa là: tất cả các trường hợp trừ trường hợp đầu tiên), instancesẽ không có giá trị nào. Mua khóa sẽ tốn kém hơn so với việc kiểm tra đơn giản, vì vậy việc kiểm tra một lần giá trị instancetrước khi khóa là một cách tối ưu hóa tốt và miễn phí.

Mẫu này được gọi là khóa được kiểm tra hai lần: http://en.wikipedia.org/wiki/Double-checked_locking


3

Jeffrey Richter khuyên bạn nên làm như sau:



    public sealed class Singleton
    {
        private static readonly Object s_lock = new Object();
        private static Singleton instance = null;
    
        private Singleton()
        {
        }
    
        public static Singleton Instance
        {
            get
            {
                if(instance != null) return instance;
                Monitor.Enter(s_lock);
                Singleton temp = new Singleton();
                Interlocked.Exchange(ref instance, temp);
                Monitor.Exit(s_lock);
                return instance;
            }
        }
    }


Không phải là làm cho biến cá thể dễ bay hơi, có làm điều tương tự không?
Ε Г И І И О

1

Bạn có thể háo hức tạo một phiên bản Singleton an toàn theo luồng, tùy thuộc vào nhu cầu ứng dụng của bạn, đây là mã ngắn gọn, mặc dù tôi muốn phiên bản lười biếng của @ andasa hơn.

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

    private Singleton() { }

    public static Singleton Instance()
    {
        return instance;
    }
}

0

Đây được gọi là cơ chế khóa Double check, trước tiên, chúng ta sẽ kiểm tra xem instance có được tạo hay không. Nếu không thì chỉ chúng tôi sẽ đồng bộ hóa phương thức và tạo cá thể. Nó sẽ cải thiện đáng kể hiệu suất của ứng dụng. Khóa thực hiện là nặng. Vì vậy, để tránh bị khóa trước tiên chúng ta cần kiểm tra giá trị null. Đây cũng là sợi chỉ an toàn và nó là cách tốt nhất để đạt được hiệu quả tốt nhất. Vui lòng xem đoạn mã sau.

public sealed class Singleton
{
    private static readonly object Instancelock = new object();
    private Singleton()
    {
    }
    private static Singleton instance = null;

    public static Singleton GetInstance
    {
        get
        {
            if (instance == null)
            {
                lock (Instancelock)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}

0

Một phiên bản khác của Singleton trong đó dòng mã sau đây tạo phiên bản Singleton tại thời điểm khởi động ứng dụng.

private static readonly Singleton singleInstance = new Singleton();

Ở đây CLR (Common Language Runtime) sẽ đảm nhận việc khởi tạo đối tượng và an toàn luồng. Điều đó có nghĩa là chúng tôi sẽ không yêu cầu viết bất kỳ mã nào một cách rõ ràng để xử lý an toàn luồng cho môi trường đa luồng.

"Việc tải Eager trong mẫu thiết kế singleton không phải là một quá trình mà trong đó chúng ta cần khởi tạo đối tượng singleton tại thời điểm khởi động ứng dụng thay vì theo yêu cầu và giữ cho nó sẵn sàng trong bộ nhớ để sử dụng trong tương lai."

public sealed class Singleton
    {
        private static int counter = 0;
        private Singleton()
        {
            counter++;
            Console.WriteLine("Counter Value " + counter.ToString());
        }
        private static readonly Singleton singleInstance = new Singleton(); 

        public static Singleton GetInstance
        {
            get
            {
                return singleInstance;
            }
        }
        public void PrintDetails(string message)
        {
            Console.WriteLine(message);
        }
    }

từ chính:

static void Main(string[] args)
        {
            Parallel.Invoke(
                () => PrintTeacherDetails(),
                () => PrintStudentdetails()
                );
            Console.ReadLine();
        }
        private static void PrintTeacherDetails()
        {
            Singleton fromTeacher = Singleton.GetInstance;
            fromTeacher.PrintDetails("From Teacher");
        }
        private static void PrintStudentdetails()
        {
            Singleton fromStudent = Singleton.GetInstance;
            fromStudent.PrintDetails("From Student");
        }

Thoải mái thay thế nhưng không trả lời câu hỏi đó là về việc kiểm tra khóa trong việc thực hiện cụ thể được đề cập trong câu hỏi
Wayne Phipps

không trực tiếp nhưng có thể được sử dụng như một "Mẫu C # Singleton An toàn cho Chủ đề".
Jaydeep Shil

0

Mô hình Singleton chống phản xạ:

public sealed class Singleton
{
    public static Singleton Instance => _lazy.Value;
    private static Lazy<Singleton, Func<int>> _lazy { get; }

    static Singleton()
    {
        var i = 0;
        _lazy = new Lazy<Singleton, Func<int>>(() =>
        {
            i++;
            return new Singleton();
        }, () => i);
    }

    private Singleton()
    {
        if (_lazy.Metadata() == 0 || _lazy.IsValueCreated)
            throw new Exception("Singleton creation exception");
    }

    public void Run()
    {
        Console.WriteLine("Singleton called");
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.