Sự cần thiết của công cụ sửa đổi dễ bay hơi trong khóa kiểm tra hai lần trong .NET


85

Nhiều văn bản nói rằng khi triển khai khóa được kiểm tra hai lần trong .NET, trường bạn đang khóa phải được áp dụng công cụ sửa đổi dễ bay hơi. Nhưng chính xác là tại sao? Xem xét ví dụ sau:

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;
      }
   }
}

tại sao "khóa (syncRoot)" không đạt được tính nhất quán bộ nhớ cần thiết? Không phải là sau khi câu lệnh "lock" cả đọc và ghi đều sẽ biến động và do đó, sự nhất quán cần thiết sẽ được thực hiện?


2
Điều này đã được nhai đi nhai lại nhiều lần rồi. yoda.arachsys.com/csharp/singleton.html
Hans Passant

1
Thật không may trong bài viết đó Jon đề cập đến 'dễ bay hơi' hai lần và không tham chiếu trực tiếp đề cập đến các ví dụ mã mà anh ta đã đưa ra.
Dan Esparza

Xem bài viết này để hiểu mối quan tâm: igoro.com/archive/volatile-keyword-in-c-memory-model-explained Về cơ bản, LÝ THUYẾT có thể JIT sử dụng thanh ghi CPU cho biến cá thể - đặc biệt nếu bạn cần một ít mã bổ sung trong đó. Do đó, thực hiện một câu lệnh if hai lần có khả năng trả về cùng một giá trị bất kể nó thay đổi trên một luồng khác. Trên thực tế câu trả lời là một chút phức tạp tuyên bố khóa có thể hoặc không thể chịu trách nhiệm làm cho mọi thứ tốt hơn ở đây (tiếp theo)
user2685937

(xem bình luận trước tiếp tục) - Đây là những gì tôi thực sự đang xảy ra - Về cơ bản, bất kỳ mã nào thực hiện bất kỳ điều gì phức tạp hơn đọc hoặc đặt một biến có thể kích hoạt JIT nói, hãy quên cố gắng tối ưu hóa điều này, hãy chỉ tải và lưu vào bộ nhớ vì nếu một hàm được gọi là JIT có khả năng cần phải lưu và tải lại thanh ghi nếu nó lưu trữ nó ở đó mỗi lần, thay vì làm điều đó nó chỉ ghi và đọc trực tiếp từ bộ nhớ mỗi lần. Làm thế nào để tôi biết khóa không có gì đặc biệt? Nhìn vào liên kết tôi đăng trong các bình luận trước đó từ Igor (tiếp theo bình luận tiếp theo)
user2685937

(xem 2 bình luận ở trên) - Tôi đã kiểm tra mã của Igor và khi nó tạo một luồng mới, tôi đã thêm một khóa xung quanh nó và thậm chí làm cho nó lặp lại. Nó vẫn không làm cho mã thoát vì biến cá thể đã được đưa ra khỏi vòng lặp. Thêm vào vòng lặp while một tập hợp biến cục bộ đơn giản vẫn kéo biến đó ra khỏi vòng lặp - Bây giờ bất cứ điều gì phức tạp hơn như câu lệnh if hoặc một lệnh gọi phương thức hoặc thậm chí là một lệnh gọi khóa sẽ ngăn cản việc tối ưu hóa và do đó làm cho nó hoạt động. Vì vậy, bất kỳ mã phức tạp nào thường buộc truy cập biến trực tiếp hơn là cho phép JIT tối ưu hóa. (tiếp theo bình luận tiếp theo)
user2685937

Câu trả lời:


59

Dễ bay hơi là không cần thiết. Chà, đại loại là **

volatileđược sử dụng để tạo hàng rào bộ nhớ * giữa các lần đọc và ghi trên biến.
lock, khi được sử dụng, gây ra các rào cản bộ nhớ được tạo ra xung quanh khối bên trong lock, ngoài việc giới hạn quyền truy cập vào khối trong một luồng.
Rào cản bộ nhớ khiến mỗi luồng đọc giá trị hiện tại nhất của biến (không phải giá trị cục bộ được lưu trong một số thanh ghi) và trình biên dịch không sắp xếp lại các câu lệnh. Việc sử dụng volatilelà không cần thiết ** vì bạn đã có khóa.

Joseph Albahari giải thích điều này theo cách tốt hơn tôi từng có thể.

Và hãy nhớ xem hướng dẫn của Jon Skeet để triển khai singleton trong C #


update :
* volatilekhiến các lần đọc biến là VolatileReads và ghi là VolatileWrites, trên x86 và x64 trên CLR, được thực hiện với a MemoryBarrier. Chúng có thể được phân loại tốt hơn trên các hệ thống khác.

** câu trả lời của tôi chỉ đúng nếu bạn đang sử dụng CLR trên bộ xử lý x86 và x64. Nó thể đúng trong các mô hình bộ nhớ khác, như trên Mono (và các triển khai khác), Itanium64 và phần cứng trong tương lai. Đây là những gì Jon đang đề cập đến trong bài viết của anh ấy trong "gotchas" cho khóa được kiểm tra hai lần.

Thực hiện một trong {đánh dấu biến là volatile, đọc nó bằng Thread.VolatileReadhoặc chèn lệnh gọi tới Thread.MemoryBarrier} có thể cần thiết để mã hoạt động bình thường trong tình huống mô hình bộ nhớ yếu.

Theo những gì tôi hiểu, trên CLR (thậm chí trên IA64), các lần viết không bao giờ được sắp xếp lại (các lần viết luôn có ngữ nghĩa phát hành). Tuy nhiên, trên IA64, các lần đọc có thể được sắp xếp lại thứ tự trước khi ghi, trừ khi chúng được đánh dấu là dễ bay hơi. Thật không may, tôi không có quyền truy cập vào phần cứng IA64 để chơi cùng, vì vậy bất cứ điều gì tôi nói về nó sẽ chỉ là suy đoán.

Tôi cũng thấy những bài viết này hữu ích:
http://www.codeproject.com/KB/tips/MemoryBarrier.aspx
Bài viết của vance morrison (mọi thứ liên kết đến điều này, nó nói về khóa được kiểm tra hai lần)
bài viết của chris brumme (mọi thứ liên kết đến điều này )
Joe Duffy: Các biến thể bị hỏng của khóa được kiểm tra kép

Loạt bài về đa luồng của luis abreu cũng cung cấp một cái nhìn tổng quan tốt đẹp về các khái niệm
http://msmvps.com/blogs/luisabreu/archive/2009/06/29/multithreading-load-and-store-reordering.aspx
http://msmvps. com / blogs / luisabreu / archive / 2009/07/03 / multithreading-Introduction-memory-domains.aspx


Jon Skeet thực sự nói rằng công cụ sửa đổi dễ bay hơi là cần thiết để tạo ra bộ nhớ thích hợp, trong khi tác giả liên kết đầu tiên nói rằng khóa (Monitor.Enter) là đủ. Thực ra ai đúng ???
Konstantin

@Konstantin có vẻ như Jon đang đề cập đến mô hình bộ nhớ trên bộ xử lý Itanium 64, vì vậy trong trường hợp đó, việc sử dụng dễ bay hơi có thể là cần thiết. Tuy nhiên, tính dễ bay hơi là không cần thiết trên bộ xử lý x86 và x64. Tôi sẽ cập nhật thêm trong một chút.
dan

Nếu khóa thực sự đang tạo ra một rào cản bộ nhớ và nếu bộ thay đổi bộ nhớ thực sự làm mất hiệu lực của cả thứ tự lệnh VÀ bộ đệm ẩn thì nó sẽ hoạt động trên tất cả các bộ xử lý. Dù sao nó rất lạ mà điều cơ bản như vậy gây ra rất nhiều rắc rối ...
Konstantin

2
Câu trả lời này có vẻ sai đối với tôi. Nếu volatilelà không cần thiết trên bất kỳ nền tảng sau đó nó sẽ có nghĩa là JIT không thể tối ưu hóa việc tải bộ nhớ object s1 = syncRoot; object s2 = syncRoot;để object s1 = syncRoot; object s2 = s1;trên nền tảng đó. Điều đó dường như rất khó xảy ra với tôi.
user541686 19/12/13

1
Ngay cả khi CLR không sắp xếp lại thứ tự ghi (tôi nghi ngờ điều đó là đúng, nhiều cách tối ưu hóa rất tốt có thể được thực hiện bằng cách làm như vậy), nó vẫn sẽ có lỗi miễn là chúng ta có thể nội tuyến lệnh gọi hàm tạo và tạo đối tượng tại chỗ (chúng ta có thể thấy một đối tượng được khởi tạo một nửa). Độc lập với bất kỳ mô hình bộ nhớ nào mà CPU bên dưới sử dụng! Theo Eric Lippert, CLR trên Intel ít nhất cũng giới thiệu một membarrier sau khi các hàm tạo từ chối tối ưu hóa đó, nhưng điều đó không được yêu cầu bởi thông số kỹ thuật và tôi sẽ không tin vào điều tương tự xảy ra trên ARM chẳng hạn ..
Voo

34

Có một cách để thực hiện nó mà không cần volatiletrường. Tôi sẽ giải thích nó ...

Tôi nghĩ rằng việc sắp xếp lại thứ tự truy cập bộ nhớ bên trong khóa là điều nguy hiểm, vì vậy bạn có thể nhận được một phiên bản được khởi tạo không hoàn chỉnh bên ngoài khóa. Để tránh điều này, tôi làm điều này:

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

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         // very fast test, without implicit memory barriers or locks
         if (instance == null)
         {
            lock (syncRoot)
            {
               if (instance == null)
               {
                    var temp = new Singleton();

                    // ensures that the instance is well initialized,
                    // and only then, it assigns the static variable.
                    System.Threading.Thread.MemoryBarrier();
                    instance = temp;
               }
            }
         }

         return instance;
      }
   }
}

Hiểu mã

Hãy tưởng tượng rằng có một số mã khởi tạo bên trong phương thức khởi tạo của lớp Singleton. Nếu các hướng dẫn này được sắp xếp lại thứ tự sau khi trường được đặt bằng địa chỉ của đối tượng mới, thì bạn có một thể hiện chưa hoàn chỉnh ... hãy tưởng tượng rằng lớp có mã này:

private int _value;
public int Value { get { return this._value; } }

private Singleton()
{
    this._value = 1;
}

Bây giờ hãy tưởng tượng một cuộc gọi đến hàm tạo bằng cách sử dụng toán tử mới:

instance = new Singleton();

Điều này có thể được mở rộng cho các hoạt động sau:

ptr = allocate memory for Singleton;
set ptr._value to 1;
set Singleton.instance to ptr;

Điều gì sẽ xảy ra nếu tôi sắp xếp lại các hướng dẫn này như thế này:

ptr = allocate memory for Singleton;
set Singleton.instance to ptr;
set ptr._value to 1;

Liệu nó có làm cho một sự khác biệt? KHÔNG nếu bạn nghĩ về một chủ đề duy nhất. nếu bạn nghĩ về nhiều chuỗi ... điều gì sẽ xảy ra nếu chuỗi được xen vào ngay sau set instance to ptr:

ptr = allocate memory for Singleton;
set Singleton.instance to ptr;
-- thread interruped here, this can happen inside a lock --
set ptr._value to 1; -- Singleton.instance is not completelly initialized

Đó là điều mà rào cản bộ nhớ tránh được, bằng cách không cho phép sắp xếp lại thứ tự truy cập bộ nhớ:

ptr = allocate memory for Singleton;
set temp to ptr; // temp is a local variable (that is important)
set ptr._value to 1;
-- memory barrier... cannot reorder writes after this point, or reads before it --
-- Singleton.instance is still null --
set Singleton.instance to temp;

Chúc bạn viết mã vui vẻ!


1
Nếu CLR cho phép truy cập vào một đối tượng trước khi nó được khởi tạo, đó là một lỗ hổng bảo mật. Hãy tưởng tượng một lớp đặc quyền có hàm tạo công khai duy nhất đặt "SecureMode = 1" và sau đó các phương thức thể hiện của nó sẽ kiểm tra điều đó. Nếu bạn có thể gọi các phương thức cá thể đó mà không chạy hàm tạo, bạn có thể thoát ra khỏi mô hình bảo mật và vi phạm hộp cát.
MichaelGG

1
@MichaelGG: trong trường hợp bạn mô tả, nếu lớp đó sẽ hỗ trợ nhiều luồng để truy cập nó, thì đó là một vấn đề. Nếu lệnh gọi phương thức khởi tạo được nội tuyến bởi jitter, thì CPU có thể sắp xếp lại các lệnh theo cách mà tham chiếu được lưu trữ trỏ đến một thể hiện chưa được khởi tạo hoàn toàn. Đây không phải là vấn đề bảo mật CLR vì nó có thể tránh được, người lập trình có trách nhiệm sử dụng: khóa liên động, rào cản bộ nhớ, khóa và / hoặc trường biến động, bên trong hàm tạo của lớp đó.
Miguel Angelo

2
Một rào cản bên trong ctor không khắc phục được nó. Nếu CLR gán tham chiếu cho đối tượng mới được cấp phát trước khi ctor hoàn thành và không chèn một membarrier, thì một luồng khác có thể thực thi một phương thức thể hiện trên một đối tượng được khởi tạo bán phần.
MichaelGG

Đây là "mẫu thay thế" mà ReSharper 2016/2017 đề xuất trong trường hợp DCL trong C #. OTOH, Java không đảm bảo rằng kết quả của newhoàn toàn khởi tạo ..
user2864740

Tôi biết rằng việc triển khai MS .net đặt một rào cản bộ nhớ ở cuối trình tạo ... nhưng tốt hơn là nên an toàn hơn là xin lỗi.
Miguel Angelo

7

Tôi không nghĩ rằng có ai đó đã thực sự trả lời câu hỏi , vì vậy tôi sẽ thử.

Cái dễ bay hơi và cái đầu tiên if (instance == null)là không "cần thiết". Khóa sẽ làm cho mã này an toàn.

Vì vậy, câu hỏi là: tại sao bạn lại thêm cái đầu tiên if (instance == null)?

Lý do có lẽ là để tránh thực thi đoạn mã bị khóa một cách không cần thiết. Trong khi bạn đang thực thi mã bên trong khóa, bất kỳ luồng nào khác cố gắng thực thi mã đó cũng bị chặn, điều này sẽ làm chậm chương trình của bạn nếu bạn cố gắng truy cập singleton thường xuyên từ nhiều luồng. Tùy thuộc vào ngôn ngữ / nền tảng, cũng có thể có chi phí phát sinh từ chính khóa mà bạn muốn tránh.

Vì vậy, kiểm tra null đầu tiên được thêm vào như một cách thực sự nhanh chóng để xem bạn có cần khóa hay không. Nếu bạn không cần tạo singleton, bạn có thể tránh khóa hoàn toàn.

Nhưng bạn không thể kiểm tra xem tham chiếu có là null hay không mà không khóa nó theo một cách nào đó, vì do bộ nhớ đệm của bộ xử lý, một luồng khác có thể thay đổi nó và bạn sẽ đọc một giá trị "cũ" dẫn đến việc bạn phải nhập khóa một cách không cần thiết. Nhưng bạn đang cố gắng tránh một ổ khóa!

Vì vậy, bạn làm cho singleton dễ bay hơi để đảm bảo rằng bạn đọc giá trị mới nhất mà không cần sử dụng khóa.

Bạn vẫn cần khóa bên trong bởi vì biến số dễ bay hơi chỉ bảo vệ bạn trong một lần truy cập duy nhất vào biến - bạn không thể kiểm tra và thiết lập nó một cách an toàn mà không sử dụng khóa.

Bây giờ, điều này thực sự hữu ích?

Tôi sẽ nói "trong hầu hết các trường hợp, không".

Nếu Singleton.Instance có thể gây ra sự kém hiệu quả do các ổ khóa, thì tại sao bạn lại gọi nó thường xuyên đến mức đây sẽ là một vấn đề nghiêm trọng ? Toàn bộ điểm của một singleton là chỉ có một, vì vậy mã của bạn có thể đọc và lưu vào bộ nhớ cache tham chiếu singleton một lần.

Trường hợp duy nhất tôi có thể nghĩ về nơi mà bộ nhớ đệm này sẽ không thể thực hiện được khi bạn có một số lượng lớn các luồng (ví dụ: một máy chủ sử dụng một luồng mới để xử lý mọi yêu cầu có thể tạo ra hàng triệu luồng chạy rất ngắn, mỗi luồng mà sẽ phải gọi Singleton.Instance một lần).

Vì vậy, tôi nghi ngờ rằng khóa kiểm tra hai lần là một cơ chế có vị trí thực sự trong các trường hợp quan trọng về hiệu suất rất cụ thể, và sau đó mọi người đã tranh luận về nhóm "đây là cách thích hợp để làm điều đó" mà không thực sự nghĩ nó làm gì và liệu nó có sẽ thực sự cần thiết trong trường hợp họ đang sử dụng nó.


6
Đây là một nơi nào đó giữa sai và thiếu điểm. volatilekhông liên quan gì đến ngữ nghĩa khóa trong khóa được kiểm tra kỹ, nó liên quan đến mô hình bộ nhớ và tính nhất quán của bộ đệm. Mục đích của nó là để đảm bảo rằng một luồng không nhận được giá trị vẫn đang được khởi tạo bởi một luồng khác, điều mà kiểu khóa kiểm tra kép vốn dĩ không ngăn cản. Trong Java, bạn chắc chắn cần volatiletừ khóa; trong .NET nó âm u, vì nó sai theo ECMA nhưng đúng theo thời gian chạy. Dù bằng cách nào, lockchắc chắn không quan tâm đến nó.
Aaronaught

Huh? Tôi không thể thấy tuyên bố của bạn không đồng ý với những gì tôi đã nói ở đâu, và tôi cũng không nói rằng sự bất ổn liên quan đến ngữ nghĩa khóa.
Jason Williams

6
Câu trả lời của bạn, giống như một số câu lệnh khác trong chuỗi này, tuyên bố rằng câu trả lời locklàm cho chuỗi mã an toàn. Điều đó đúng, nhưng kiểu khóa kiểm tra hai lần có thể khiến nó không an toàn . Đó là những gì bạn dường như đang thiếu. Câu trả lời này dường như quanh co về ý nghĩa và mục đích của khóa kiểm tra hai lần mà không giải quyết các vấn đề an toàn luồng là lý do volatile.
Aaronaught

1
Làm thế nào nó có thể làm cho không an toàn nếu instanceđược đánh dấu bằng volatile?
UserControl

5

Bạn nên sử dụng biến động với mô hình khóa kiểm tra hai lần.

Hầu hết mọi người coi bài viết này là bằng chứng rằng bạn không cần phải dễ bay hơi: https://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S10

Nhưng họ không đọc đến cuối: " Lời cảnh báo cuối cùng - Tôi chỉ đoán mô hình bộ nhớ x86 từ hành vi quan sát được trên các bộ xử lý hiện có. Vì vậy, các kỹ thuật khóa thấp cũng rất mong manh vì phần cứng và trình biên dịch có thể trở nên hung hăng hơn theo thời gian . Dưới đây là một số chiến lược để giảm thiểu tác động của sự mong manh này đối với mã của bạn. Đầu tiên, bất cứ khi nào có thể, hãy tránh các kỹ thuật khóa thấp. . "

Nếu bạn cần thuyết phục hơn thì hãy đọc bài viết này về thông số ECMA sẽ được sử dụng cho các nền tảng khác: msdn.microsoft.com/en-us/magazine/jj863136.aspx

Nếu bạn cần thuyết phục hơn nữa, hãy đọc bài viết mới hơn này rằng các tối ưu hóa có thể được đưa vào để ngăn nó hoạt động mà không dễ bay hơi: msdn.microsoft.com/en-us/magazine/jj883956.aspx

Tóm lại, nó "có thể" làm việc cho bạn mà không dễ bay hơi trong thời điểm này, nhưng đừng có khả năng nó viết mã thích hợp và sử dụng phương thức dễ bay hơi hoặc biến động đọc / ghi. Các bài viết đề xuất làm theo cách khác đôi khi bỏ qua một số rủi ro có thể xảy ra đối với việc tối ưu hóa JIT / trình biên dịch có thể ảnh hưởng đến mã của bạn, cũng như chúng tôi, các tối ưu hóa trong tương lai có thể xảy ra có thể phá vỡ mã của bạn. Cũng như các giả định đã đề cập trong bài viết trước, các giả định về hoạt động mà không có biến động đã có thể không giữ được ARM.


1
Câu trả lời tốt. Câu trả lời đúng duy nhất cho câu hỏi này là đơn giản "Không". Theo đó, câu trả lời được chấp nhận là sai.
Dennis Kassel

3

AFAIK (và - hãy thận trọng với điều này, tôi không làm nhiều việc đồng thời) không. Khóa chỉ cung cấp cho bạn sự đồng bộ hóa giữa nhiều đối thủ (luồng).

Mặt khác, dễ bay hơi yêu cầu máy của bạn đánh giá lại giá trị mỗi lần để bạn không gặp phải giá trị được lưu trong bộ nhớ cache (và sai).

Xem http://msdn.microsoft.com/en-us/library/ms998558.aspx và lưu ý trích dẫn sau:

Ngoài ra, biến được khai báo là dễ bay hơi để đảm bảo rằng việc gán cho biến cá thể hoàn tất trước khi biến cá thể có thể được truy cập.

Mô tả về tính dễ bay hơi: http://msdn.microsoft.com/en-us/library/x13ttww7%28VS.71%29.aspx


2
Một 'khóa' cũng cung cấp một rào cản bộ nhớ, giống như (hoặc tốt hơn) dễ bay hơi.
Henk Holterman

2

Tôi nghĩ rằng tôi đã tìm thấy những gì tôi đang tìm kiếm. Thông tin chi tiết có trong bài viết này - http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S10 .

Tóm lại - trong .NET công cụ sửa đổi dễ bay hơi thực sự không cần thiết trong tình huống này. Tuy nhiên, trong các mô hình bộ nhớ yếu hơn, việc ghi được thực hiện trong phương thức khởi tạo của đối tượng khởi tạo lười biếng có thể bị trì hoãn sau khi ghi vào trường, vì vậy các luồng khác có thể đọc phiên bản non-null bị hỏng trong câu lệnh if đầu tiên.


1
Ở cuối bài viết đó, hãy đọc kỹ, đặc biệt là câu cuối cùng tác giả nói: "Lời cảnh báo cuối cùng - Tôi chỉ đoán ở mô hình bộ nhớ x86 từ hành vi quan sát được trên các bộ xử lý hiện có. Vì vậy, các kỹ thuật khóa thấp cũng rất mong manh vì phần cứng và trình biên dịch có thể trở nên tích cực hơn theo thời gian. Dưới đây là một số chiến lược để giảm thiểu tác động của sự mong manh này lên mã của bạn. Đầu tiên, bất cứ khi nào có thể, hãy tránh các kỹ thuật khóa thấp. (...) Cuối cùng, giả sử mô hình bộ nhớ yếu nhất có thể, sử dụng các khai báo dễ bay hơi thay vì dựa vào các đảm bảo ngầm. "
user2685937

1
Nếu bạn cần thuyết phục hơn thì hãy đọc bài viết này về thông số ECMA sẽ được sử dụng cho các nền tảng khác: msdn.microsoft.com/en-us/magazine/jj863136.aspx Nếu bạn cần thuyết phục hơn nữa, hãy đọc bài viết mới hơn này để tối ưu hóa có thể được đưa vào điều đó ngăn nó hoạt động mà không dễ bay hơi: msdn.microsoft.com/en-us/magazine/jj883956.aspx Tóm lại, nó "có thể" hoạt động cho bạn mà không dễ bay hơi vào lúc này, nhưng đừng có cơ hội nó viết mã thích hợp và sử dụng dễ bay hơi hoặc các phương thức đọc / ghi dễ bay hơi.
user2685937

1

lockđủ. Bản thân đặc tả ngôn ngữ MS (3.0) đề cập đến kịch bản chính xác này trong §8.12, mà không đề cập đến volatile:

Một cách tiếp cận tốt hơn là đồng bộ hóa quyền truy cập vào dữ liệu tĩnh bằng cách khóa một đối tượng tĩnh riêng. Ví dụ:

class Cache
{
    private static object synchronizationObject = new object();
    public static void Add(object x) {
        lock (Cache.synchronizationObject) {
          ...
        }
    }
    public static void Remove(object x) {
        lock (Cache.synchronizationObject) {
          ...
        }
    }
}

Jon Skeet trong bài báo của mình ( yoda.arachsys.com/csharp/singleton.html ) nói rằng tính dễ bay hơi là cần thiết cho hàng rào bộ nhớ thích hợp trong trường hợp này. Marc, bạn có thể bình luận về điều này?
Konstantin

Ah, tôi đã không nhận thấy khóa được kiểm tra hai lần; đơn giản: đừng làm điều đó ;-p
Marc Gravell

Tôi thực sự nghĩ rằng khóa được kiểm tra hai lần là một điều tốt về hiệu suất. Ngoài ra nếu nó là cần thiết để tạo ra một lĩnh vực không ổn định, trong khi nó được truy cập trong vòng khóa, sau đó nhấp đúp cheked khóa không phải là tồi tệ hơn nhiều so với bất kỳ khóa khác ...
Konstantin

Nhưng liệu nó có tốt như cách tiếp cận lớp học riêng biệt mà Jon đề cập?
Marc Gravell

-3

Đây là một bài đăng khá hay về việc sử dụng dễ bay hơi với khóa được kiểm tra hai lần:

http://tech.puredanger.com/2007/06/15/double-checked-locking/

Trong Java, nếu mục đích là bảo vệ một biến, bạn không cần phải khóa nếu nó được đánh dấu là dễ bay hơi


3
Thú vị, nhưng không nhất thiết phải rất hữu ích. Mô hình bộ nhớ của JVM và mô hình bộ nhớ của CLR không giống nhau.
bcat
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.