Sự khác biệt giữa dễ bay hơi và đồng bộ hóa trong Java


233

Tôi đang tự hỏi sự khác biệt giữa việc khai báo một biến là volatilevà luôn truy cập vào biến trong một synchronized(this)khối trong Java?

Theo bài viết này, http://www.javamex.com/tutorials/syn syncization_voliverse.shtml có rất nhiều điều để nói và có nhiều điểm khác biệt nhưng cũng có một số điểm tương đồng.

Tôi đặc biệt quan tâm đến phần thông tin này:

...

  • truy cập vào một biến dễ bay hơi không bao giờ có khả năng chặn: chúng ta chỉ thực hiện đọc hoặc ghi đơn giản, vì vậy không giống như một khối được đồng bộ hóa, chúng ta sẽ không bao giờ giữ bất kỳ khóa nào;
  • bởi vì việc truy cập một biến dễ bay hơi không bao giờ giữ khóa, nó không phù hợp với các trường hợp chúng ta muốn đọc-cập nhật-ghi như một hoạt động nguyên tử (trừ khi chúng ta chuẩn bị "bỏ lỡ một bản cập nhật");

Ý nghĩa của việc đọc-cập nhật-ghi là gì? Không phải là một bản ghi cũng là một bản cập nhật hay đơn giản là chúng có nghĩa là bản cập nhật đó là một bản ghi phụ thuộc vào bài đọc?

Trên hết, khi nào thì phù hợp hơn để khai báo các biến volatilethay vì truy cập chúng thông qua một synchronizedkhối? Có phải là một ý tưởng tốt để sử dụng volatilecho các biến phụ thuộc vào đầu vào? Chẳng hạn, có một biến được gọi renderlà được đọc qua vòng kết xuất và được đặt bởi một sự kiện nhấn phím?

Câu trả lời:


383

Điều quan trọng là phải hiểu rằng có hai khía cạnh về an toàn luồng.

  1. kiểm soát thực thi, và
  2. khả năng hiển thị bộ nhớ

Việc đầu tiên phải thực hiện với việc kiểm soát khi mã thực thi (bao gồm cả thứ tự thực hiện các lệnh) và liệu nó có thể thực thi đồng thời hay không, và thứ hai phải làm khi các hiệu ứng trong bộ nhớ của những gì đã được thực hiện được hiển thị cho các luồng khác. Vì mỗi CPU có một số mức bộ đệm giữa nó và bộ nhớ chính, nên các luồng chạy trên các CPU hoặc lõi khác nhau có thể thấy "bộ nhớ" khác nhau tại bất kỳ thời điểm nào do các luồng được phép lấy và hoạt động trên các bản sao riêng của bộ nhớ chính.

Việc sử dụng synchronizedngăn không cho bất kỳ luồng nào khác có được màn hình (hoặc khóa) cho cùng một đối tượng , do đó ngăn tất cả các khối mã được bảo vệ bằng cách đồng bộ hóa trên cùng một đối tượng thực hiện đồng thời. Đồng bộ hóa cũng tạo ra một rào cản bộ nhớ "xảy ra trước", gây ra hạn chế về khả năng hiển thị bộ nhớ sao cho mọi thứ được thực hiện đến thời điểm một số luồng phát hành khóa xuất hiện cho một luồng khác sau đó có được khóa tương tự đã xảy ra trước khi khóa. Về mặt thực tế, trên phần cứng hiện tại, điều này thường gây ra lỗi bộ đệm CPU khi có màn hình và ghi vào bộ nhớ chính khi nó được phát hành, cả hai đều đắt (tương đối).

volatileMặt khác, sử dụng , buộc tất cả các truy cập (đọc hoặc ghi) vào biến dễ bay hơi xảy ra với bộ nhớ chính, giữ cho biến dễ bay ra khỏi bộ đệm CPU. Điều này có thể hữu ích cho một số hành động trong đó đơn giản là yêu cầu rằng khả năng hiển thị của biến là chính xác và thứ tự truy cập không quan trọng. Sử dụng volatilecũng thay đổi điều trị longdoubleyêu cầu truy cập vào chúng là nguyên tử; trên một số phần cứng (cũ hơn), điều này có thể yêu cầu khóa, mặc dù không phải trên phần cứng 64 bit hiện đại. Trong mô hình bộ nhớ mới (JSR-133) cho Java 5+, ngữ nghĩa của tính không ổn định đã được tăng cường mạnh mẽ gần như được đồng bộ hóa đối với khả năng hiển thị bộ nhớ và sắp xếp lệnh (xem http://www.cs.umd.edu /users/pugh/java/memoryModel/jsr-133-faq.html#voliverse). Đối với mục đích hiển thị, mỗi quyền truy cập vào trường biến động hoạt động giống như một nửa đồng bộ hóa.

Trong mô hình bộ nhớ mới, vẫn đúng là các biến dễ bay hơi không thể được sắp xếp lại với nhau. Sự khác biệt là bây giờ không còn dễ dàng để sắp xếp lại các truy cập trường bình thường xung quanh chúng. Viết vào trường dễ bay hơi có hiệu ứng bộ nhớ tương tự như bản phát hành màn hình và đọc từ trường dễ bay hơi có hiệu ứng bộ nhớ tương tự như màn hình thu được. Trong thực tế, bởi vì mô hình bộ nhớ mới đặt các ràng buộc chặt chẽ hơn trong việc sắp xếp lại các truy cập trường dễ bay hơi với các truy cập trường khác, dễ bay hơi hoặc không, bất cứ điều gì có thể nhìn thấy đối với luồng Akhi nó ghi vào trường dễ bay hơi fsẽ hiển thị cho luồng Bkhi đọc f.

- Câu hỏi thường gặp về JSR 133 (Mô hình bộ nhớ Java)

Vì vậy, bây giờ cả hai dạng rào cản bộ nhớ (theo JMM hiện tại) đều tạo ra một rào cản sắp xếp lại lệnh để ngăn trình biên dịch hoặc thời gian chạy sắp xếp lại các lệnh theo hàng rào. Trong JMM cũ, không ổn định không ngăn cản việc đặt hàng lại. Điều này có thể quan trọng, bởi vì ngoài các rào cản bộ nhớ, giới hạn duy nhất được đặt ra là, đối với bất kỳ luồng cụ thể nào , hiệu ứng ròng của mã cũng giống như nếu các lệnh được thực thi theo thứ tự chính xác mà chúng xuất hiện trong nguồn.

Một cách sử dụng dễ bay hơi là cho một đối tượng được chia sẻ nhưng không thay đổi được tạo lại một cách nhanh chóng, với nhiều luồng khác tham chiếu đến đối tượng tại một điểm cụ thể trong chu kỳ thực hiện của chúng. Người ta cần các luồng khác để bắt đầu sử dụng đối tượng được tạo lại sau khi nó được xuất bản, nhưng không cần thêm chi phí đồng bộ hóa đầy đủ và đó là sự tranh chấp tùy ý và xóa bộ đệm.

// Declaration
public class SharedLocation {
    static public SomeObject someObject=new SomeObject(); // default object
    }

// Publishing code
// Note: do not simply use SharedLocation.someObject.xxx(), since although
//       someObject will be internally consistent for xxx(), a subsequent 
//       call to yyy() might be inconsistent with xxx() if the object was 
//       replaced in between calls.
SharedLocation.someObject=new SomeObject(...); // new object is published

// Using code
private String getError() {
    SomeObject myCopy=SharedLocation.someObject; // gets current copy
    ...
    int cod=myCopy.getErrorCode();
    String txt=myCopy.getErrorText();
    return (cod+" - "+txt);
    }
// And so on, with myCopy always in a consistent state within and across calls
// Eventually we will return to the code that gets the current SomeObject.

Nói với câu hỏi đọc-cập nhật-viết của bạn, cụ thể. Hãy xem xét các mã không an toàn sau:

public void updateCounter() {
    if(counter==1000) { counter=0; }
    else              { counter++; }
    }

Bây giờ, với phương thức updateCorer () không được đồng bộ hóa, hai luồng có thể nhập cùng một lúc. Trong số nhiều hoán vị của những gì có thể xảy ra, một là luồng-1 thực hiện kiểm tra bộ đếm == 1000 và thấy nó đúng và sau đó bị treo. Sau đó, luồng-2 thực hiện cùng một bài kiểm tra và cũng thấy nó đúng và bị treo. Sau đó, thread-1 tiếp tục và đặt bộ đếm thành 0. Sau đó, thread-2 lại tiếp tục đặt bộ đếm thành 0 vì nó đã bỏ lỡ bản cập nhật từ thread-1. Điều này cũng có thể xảy ra ngay cả khi chuyển đổi luồng không xảy ra như tôi đã mô tả, nhưng đơn giản là vì hai bản sao bộ đếm được lưu trong bộ nhớ cache khác nhau có mặt trong hai lõi CPU khác nhau và mỗi luồng chạy trên một lõi riêng biệt. Đối với vấn đề đó, một luồng có thể có bộ đếm ở một giá trị và luồng kia có thể có bộ đếm ở một giá trị hoàn toàn khác chỉ vì bộ nhớ đệm.

Điều quan trọng trong ví dụ này là bộ đếm biến được đọc từ bộ nhớ chính vào bộ đệm, được cập nhật trong bộ đệm và chỉ được ghi lại vào bộ nhớ chính tại một số điểm không xác định sau đó khi xảy ra rào cản bộ nhớ hoặc khi cần bộ nhớ đệm cho thứ khác. Việc tạo bộ đếm volatilelà không đủ cho tính an toàn của luồng của mã này, bởi vì kiểm tra mức tối đa và các bài tập là các hoạt động riêng biệt, bao gồm cả phần tăng là một tập hợp các read+increment+writehướng dẫn máy không nguyên tử , đại loại như:

MOV EAX,counter
INC EAX
MOV counter,EAX

Các biến dễ bay hơi chỉ hữu ích khi tất cả các hoạt động được thực hiện trên chúng là "nguyên tử", chẳng hạn như ví dụ của tôi trong đó tham chiếu đến một đối tượng được hình thành đầy đủ chỉ được đọc hoặc viết (và thực tế, thông thường, nó chỉ được viết từ một điểm duy nhất). Một ví dụ khác sẽ là một tham chiếu mảng dễ bay hơi sao lưu danh sách sao chép trên ghi, với điều kiện mảng chỉ được đọc bằng cách lấy một bản sao cục bộ của tham chiếu đến nó.


5
Cảm ơn rất nhiều! Ví dụ với bộ đếm rất đơn giản để hiểu. Tuy nhiên, khi mọi thứ trở thành sự thật, nó hơi khác một chút.
Albus Dumbledore

"Về mặt thực tế, trên phần cứng hiện tại, điều này thường gây ra lỗi bộ đệm CPU khi có màn hình và ghi vào bộ nhớ chính khi phát hành, cả hai đều đắt tiền (tương đối nói)." . Khi bạn nói bộ đệm CPU, nó có giống như Java Stacks cục bộ cho từng luồng không? hoặc một chủ đề có phiên bản địa phương của Heap? Xin lỗi nếu tôi đang ngớ ngẩn ở đây.
NishM

1
@ Vecm Nó không giống nhau, nhưng nó sẽ bao gồm các bộ nhớ cache cục bộ của các chủ đề liên quan. .
Lawrence Dol

1
@ MarianPaździoch: Tăng hoặc giảm KHÔNG phải là đọc hay viết, đó là đọc viết; đó là đọc vào một thanh ghi, sau đó tăng đăng ký, sau đó ghi lại vào bộ nhớ. Đọc và viết là nguyên tử riêng lẻ , nhưng nhiều hoạt động như vậy thì không.
Lawrence Dol

2
Vì vậy, theo FAQ, không chỉ các hành động được thực hiện kể từ khi mua khóa được hiển thị sau khi mở khóa, mà tất cả các hành động được thực hiện bởi luồng đó đều được hiển thị. Ngay cả các hành động được thực hiện trước khi mua khóa.
Lii

97

volility là một công cụ sửa đổi trường , trong khi đồng bộ hóa sửa đổi các khối mãphương thức . Vì vậy, chúng tôi có thể chỉ định ba biến thể của một trình truy cập đơn giản bằng hai từ khóa đó:

    int i1;
    int geti1() {return i1;}

    volatile int i2;
    int geti2() {return i2;}

    int i3;
    synchronized int geti3() {return i3;}

geti1()truy cập giá trị hiện được lưu trữ trong i1luồng hiện tại. Các luồng có thể có các bản sao biến cục bộ và dữ liệu không phải giống với dữ liệu được giữ trong các luồng khác. Đặc biệt, một luồng khác có thể đã được cập nhật i1trong luồng của nó, nhưng giá trị trong luồng hiện tại có thể khác với luồng đó giá trị cập nhật. Trong thực tế, Java có ý tưởng về bộ nhớ "chính" và đây là bộ nhớ chứa giá trị "chính xác" hiện tại cho các biến. Các luồng có thể có bản sao dữ liệu của riêng chúng cho các biến và bản sao luồng có thể khác với bộ nhớ "chính". Vì vậy, trên thực tế, bộ nhớ "chính" có thể có giá trị 1 cho i1, cho thread1 có giá trị 2 cho i1và cho thread2để có một giá trị 3 cho i1nếu thread1thread2 có cả i1 cập nhật nhưng những giá trị được cập nhật chưa được tuyên truyền cho bộ nhớ "chính" hoặc các chủ đề khác.

Mặt khác, geti2()truy cập hiệu quả giá trị củai2 từ bộ nhớ "chính". Một biến dễ bay hơi không được phép có một bản sao cục bộ của một biến khác với giá trị hiện được giữ trong bộ nhớ "chính". Thực tế, một biến được khai báo biến động phải có dữ liệu được đồng bộ hóa trên tất cả các luồng, để bất cứ khi nào bạn truy cập hoặc cập nhật biến trong bất kỳ luồng nào, tất cả các luồng khác sẽ thấy ngay giá trị đó. Thông thường các biến dễ bay hơi có quyền truy cập và cập nhật cao hơn các biến "đơn giản". Nói chung các chủ đề được phép có bản sao dữ liệu riêng của họ là cho hiệu quả tốt hơn.

Có hai sự khác biệt giữa volitile và sync.

Đầu tiên được đồng bộ hóa và giải phóng các khóa trên màn hình, chỉ có thể buộc một luồng tại một thời điểm để thực thi một khối mã. Đó là khía cạnh khá nổi tiếng để đồng bộ hóa. Nhưng đồng bộ hóa cũng đồng bộ hóa bộ nhớ. Trong thực tế, đồng bộ hóa toàn bộ bộ nhớ luồng với bộ nhớ "chính". Vì vậy, thực hiện geti3()làm như sau:

  1. Các chủ đề có được khóa trên màn hình cho đối tượng này.
  2. Bộ nhớ luồng xóa tất cả các biến của nó, tức là nó có tất cả các biến được đọc hiệu quả từ bộ nhớ "chính".
  3. Khối mã được thực thi (trong trường hợp này đặt giá trị trả về thành giá trị hiện tại của i3, có thể vừa được đặt lại từ bộ nhớ "chính").
  4. (Mọi thay đổi đối với các biến thường sẽ được ghi vào bộ nhớ "chính", nhưng đối với geti3 () chúng tôi không có thay đổi nào.)
  5. Các chủ đề phát hành khóa trên màn hình cho đối tượng này.

Vì vậy, khi biến động chỉ đồng bộ hóa giá trị của một biến giữa bộ nhớ luồng và bộ nhớ "chính", đồng bộ hóa giá trị của tất cả các biến giữa bộ nhớ luồng và bộ nhớ "chính", khóa và giải phóng màn hình để khởi động. Đồng bộ rõ ràng có khả năng có nhiều chi phí hơn là không ổn định.

http://javaEx.blogspot.com/2007/12/difference-b between-voliverse-and.html


35
-1, Volility không thu được khóa, nó sử dụng kiến ​​trúc CPU bên dưới để đảm bảo khả năng hiển thị trên tất cả các luồng sau khi ghi.
Michael Barker

Điều đáng chú ý là có thể có một số trường hợp khóa có thể được sử dụng để đảm bảo tính nguyên tử của ghi. Ví dụ: viết dài trên nền tảng 32 bit không hỗ trợ quyền chiều rộng mở rộng. Intel tránh điều này bằng cách sử dụng các thanh ghi SSE2 (rộng 128 bit) để xử lý các khoảng thời gian dài dễ bay hơi. Tuy nhiên, việc xem xét một biến động như một khóa có thể sẽ dẫn đến các lỗi khó chịu trong mã của bạn.
Michael Barker

2
Ngữ nghĩa quan trọng được chia sẻ bằng cách khóa các biến dễ bay hơi là cả hai đều cung cấp các cạnh Happens-Before (Java 1.5 trở lên). Nhập một khối được đồng bộ hóa, lấy ra một khóa và đọc từ một chất dễ bay hơi đều được coi là "thu nhận" và giải phóng một khóa, thoát khỏi một khối được đồng bộ hóa và viết một biến động là tất cả các dạng của "phát hành".
Michael Barker

20

synchronizedlà công cụ sửa đổi giới hạn truy cập cấp độ / khối phương thức. Nó sẽ đảm bảo rằng một chủ đề sở hữu khóa cho phần quan trọng. Chỉ các chủ đề, sở hữu một khóa có thể nhập synchronizedkhối. Nếu các luồng khác đang cố truy cập vào phần quan trọng này, chúng phải đợi cho đến khi chủ sở hữu hiện tại phát hành khóa.

volatilelà công cụ sửa đổi truy cập biến để buộc tất cả các luồng lấy giá trị mới nhất của biến từ bộ nhớ chính. Không cần khóa để truy cập volatilecác biến. Tất cả các chủ đề có thể truy cập giá trị biến dễ bay hơi cùng một lúc.

Một ví dụ tốt để sử dụng biến dễ bay hơi: Datebiến.

Giả sử rằng bạn đã thực hiện biến ngày volatile. Tất cả các luồng, truy cập vào biến này luôn nhận được dữ liệu mới nhất từ ​​bộ nhớ chính để tất cả các luồng hiển thị giá trị Ngày thực (thực tế). Bạn không cần các chủ đề khác nhau hiển thị thời gian khác nhau cho cùng một biến. Tất cả các chủ đề sẽ hiển thị đúng giá trị ngày.

nhập mô tả hình ảnh ở đây

Hãy xem bài viết này để hiểu rõ hơn về volatilekhái niệm.

Lawrence Dol rõ ràng giải thích của bạn read-write-update query.

Về các truy vấn khác của bạn

Khi nào thì phù hợp hơn để khai báo các biến dễ bay hơi hơn là truy cập chúng thông qua đồng bộ hóa?

Bạn phải sử dụng volatilenếu bạn nghĩ rằng tất cả các luồng sẽ nhận được giá trị thực của biến trong thời gian thực như ví dụ tôi đã giải thích cho biến Ngày.

Có phải là một ý tưởng tốt để sử dụng biến động cho các biến phụ thuộc vào đầu vào?

Câu trả lời sẽ giống như trong truy vấn đầu tiên.

Tham khảo bài viết này để hiểu rõ hơn.


Vì vậy, việc đọc có thể xảy ra cùng một lúc và tất cả các luồng sẽ đọc giá trị mới nhất vì CPU không lưu bộ nhớ chính vào bộ đệm của luồng CPU, nhưng còn ghi thì sao? Viết phải không đồng thời đúng? Câu hỏi thứ hai: nếu một khối được đồng bộ hóa, nhưng biến không biến động, giá trị của biến trong khối được đồng bộ hóa vẫn có thể được thay đổi bởi một luồng khác trong khối mã khác phải không?
the_prole

11

tl; dr :

Có 3 vấn đề chính với đa luồng:

1) Điều kiện cuộc đua

2) Bộ nhớ đệm / cũ

3) Tối ưu hóa CPU và CPU

volatilecó thể giải quyết 2 & 3, nhưng không thể giải quyết 1. synchronized/ khóa rõ ràng có thể giải quyết 1, 2 & 3.

Xây dựng :

1) Xem xét chủ đề này không an toàn mã:

x++;

Mặc dù nó có thể trông giống như một thao tác, nhưng thực tế là 3: đọc giá trị hiện tại của x từ bộ nhớ, thêm 1 vào nó và lưu lại vào bộ nhớ. Nếu một vài luồng cố gắng thực hiện cùng một lúc, kết quả của hoạt động không được xác định. Nếu xban đầu là 1, sau khi 2 luồng vận hành mã, nó có thể là 2 và có thể là 3, tùy thuộc vào luồng nào đã hoàn thành phần nào của thao tác trước khi điều khiển được chuyển sang luồng khác. Đây là một hình thức của điều kiện chủng tộc .

Việc sử dụng synchronizedtrên một khối mã làm cho nó trở thành nguyên tử - có nghĩa là nó làm cho nó như thể 3 hoạt động xảy ra cùng một lúc, và không có cách nào để một luồng khác đến giữa và can thiệp. Vì vậy, nếu xlà 1 và 2 luồng cố gắng tạo khuôn trước, x++chúng ta biết cuối cùng nó sẽ bằng 3. Vì vậy, nó giải quyết vấn đề điều kiện cuộc đua.

synchronized (this) {
   x++; // no problem now
}

Đánh dấu xvolatilekhông tạo ra x++;nguyên tử, vì vậy nó không giải quyết vấn đề này.

2) Ngoài ra, các luồng có ngữ cảnh riêng - tức là chúng có thể lưu các giá trị từ bộ nhớ chính. Điều đó có nghĩa là một vài luồng có thể có các bản sao của một biến, nhưng chúng hoạt động trên bản sao làm việc của chúng mà không chia sẻ trạng thái mới của biến giữa các luồng khác.

Hãy xem xét điều đó trên một chủ đề , x = 10;. Và phần nào sau đó, trong một chủ đề khác , x = 20;. Sự thay đổi giá trị của xcó thể không xuất hiện trong luồng đầu tiên, bởi vì luồng khác đã lưu giá trị mới vào bộ nhớ làm việc của nó, nhưng không sao chép nó vào bộ nhớ chính. Hoặc là nó đã sao chép nó vào bộ nhớ chính, nhưng luồng đầu tiên chưa cập nhật bản sao làm việc của nó. Vì vậy, nếu bây giờ các chủ đề đầu tiên kiểm tra if (x == 20)câu trả lời sẽ được false.

Đánh dấu một biến là volatilevề cơ bản chỉ cho tất cả các luồng thực hiện các thao tác đọc và ghi trên bộ nhớ chính. synchronizedbáo cho mọi luồng để cập nhật giá trị của chúng từ bộ nhớ chính khi chúng vào khối và đưa kết quả trở lại bộ nhớ chính khi chúng thoát khỏi khối.

Lưu ý rằng không giống như các cuộc đua dữ liệu, bộ nhớ cũ không dễ dàng tạo ra, vì việc xả vào bộ nhớ chính vẫn xảy ra.

3) Trình biên dịch và CPU có thể (không có bất kỳ hình thức đồng bộ hóa nào giữa các luồng) coi tất cả các mã là một luồng. Có nghĩa là nó có thể xem xét một số mã, điều đó rất có ý nghĩa trong khía cạnh đa luồng và coi nó như là một luồng đơn, trong đó nó không quá ý nghĩa. Vì vậy, nó có thể xem xét một mã và quyết định, để tối ưu hóa, sắp xếp lại nó hoặc thậm chí loại bỏ hoàn toàn các phần của nó, nếu nó không biết rằng mã này được thiết kế để hoạt động trên nhiều luồng.

Hãy xem xét các mã sau đây:

boolean b = false;
int x = 10;

void threadA() {
    x = 20;
    b = true;
}

void threadB() {
    if (b) {
        System.out.println(x);
    }
}

Bạn sẽ nghĩ rằng threadB chỉ có thể in 20 (hoặc hoàn toàn không in bất cứ điều gì nếu threadB if-check được thực thi trước khi đặt bthành true), như bđược đặt thành true chỉ sau khi xđược đặt thành 20, nhưng trình biên dịch / CPU có thể quyết định sắp xếp lại threadA, trong trường hợp đó threadB cũng có thể in 10. Đánh dấu bvolatileđảm bảo rằng nó sẽ không được sắp xếp lại (hoặc bị loại bỏ trong một số trường hợp nhất định). Có nghĩa là threadB chỉ có thể in 20 (hoặc không có gì cả). Đánh dấu các phương thức là syncrhonized sẽ đạt được kết quả tương tự. Đồng thời đánh dấu một biến là volatilechỉ đảm bảo rằng nó sẽ không được sắp xếp lại, nhưng mọi thứ trước / sau nó vẫn có thể được sắp xếp lại, do đó đồng bộ hóa có thể phù hợp hơn trong một số trường hợp.

Lưu ý rằng trước Mô hình bộ nhớ mới Java 5, không ổn định đã không giải quyết được vấn đề này.


1
"Mặc dù nó có thể trông giống như một thao tác, nhưng thực tế là 3: đọc giá trị hiện tại của x từ bộ nhớ, thêm 1 vào nó và lưu lại vào bộ nhớ." - Phải, vì các giá trị từ bộ nhớ phải đi qua mạch CPU để được thêm / sửa đổi. Mặc dù điều này chỉ biến thành một INChoạt động hội duy nhất , các hoạt động CPU bên dưới vẫn gấp 3 lần và yêu cầu khóa để đảm bảo an toàn cho luồng. Điểm tốt. Mặc dù, các INC/DEClệnh có thể được gắn cờ nguyên tử trong lắp ráp và vẫn là 1 hoạt động nguyên tử.
Zombie

@Zombie vì vậy khi tôi tạo một khối được đồng bộ hóa cho x ++, nó có biến nó thành một nguyên tử INC / DEC được gắn cờ hay nó sử dụng khóa thông thường?
David Refaeli

Tôi không biết! Những gì tôi biết là INC / DEC không phải là nguyên tử vì đối với CPU, nó phải tải giá trị và ĐỌC nó và cũng VIẾT nó (vào bộ nhớ), giống như bất kỳ hoạt động số học nào khác.
Zombie
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.