2015-02-05 17 views
11

Giả sử có một lớp như thế này:Java Memory Model: Trộn không đồng bộ và đồng bộ

public void MyClass { 
    private boolean someoneTouchedMeWhenIWasWorking; 

    public void process() { 
     someoneTouchedMeWhenIWasWorking = false; 
     doStuff(); 
     synchronized(this) { 
      if (someoneTouchedMeWhenIWasWorking) { 
       System.out.println("Hey!"); 
      } 
     } 
    } 

    synchronized public void touch() { 
     someoneTouchedMeWhenIWasWorking = true; 
    } 
} 

Một chủ đề kêu gọi process, một cuộc gọi touch. Lưu ý cách process xóa cờ mà không đồng bộ hóa khi bắt đầu.

Với mô hình bộ nhớ Java, có thể nào thread chạy process sẽ thấy tác dụng của ghi địa phương, không đồng bộ, mặc dù touch xảy ra sau?

Nghĩa là, nếu các chủ đề thực hiện theo thứ tự này:

T1: someoneTouchedMeWhenIWasWorking = false; // Unsynchronized 
T2: someoneTouchedMeWhenIWasWorking = true; // Synchronized 
T1: if (someoneTouchedMeWhenIWasWorking) // Synchronized 

... là nó bao giờ có thể là T1 sẽ thấy giá trị từ bộ nhớ cache cục bộ của nó? Nó có thể tuôn ra những gì nó có để bộ nhớ đầu tiên (ghi đè bất cứ điều gì T2 đã viết), và tải lại giá trị đó từ bộ nhớ?

Có cần phải đồng bộ hóa ghi đầu tiên hoặc biến biến động không?

Tôi thực sự đánh giá cao nếu câu trả lời được hỗ trợ bởi tài liệu hoặc một số nguồn đáng kính.

Trả lời

1

Khối đồng bộ trong quá trình() đảm bảo rằng giá trị mới nhất từ ​​bộ nhớ chính được chọn. Do đó mã là an toàn vì nó được.

Đây là trường hợp sử dụng tốt cho biến dễ bay hơi. "someoneTouchedMeWhenIWasWorking" nên ít nhất là biến.

Trong mã ví dụ của bạn, đồng bộ hóa được sử dụng để bảo vệ "someoneTouchedMeWhenIWasWorking" không có gì khác ngoài cờ. Nếu nó được tạo ra dễ bay hơi, khối/phương thức "đồng bộ" có thể được gỡ bỏ.

+1

Câu hỏi không phải là về cách làm cho mã an toàn, nhưng nó có an toàn không? – icza

+0

Khối đồng bộ trong quá trình() đảm bảo rằng giá trị mới nhất từ ​​bộ nhớ chính được chọn. Do đó mã là an toàn vì nó được. –

1

Để giải thích khi giá trị được cập nhật, hãy để tôi trích dẫn từ JSR-133 FAQ này:

Nhưng có nhiều hơn để đồng bộ hóa hơn loại trừ lẫn nhau. Đồng bộ hóa đảm bảo rằng bộ nhớ ghi bởi một luồng trước hoặc trong một khối đồng bộ được hiển thị theo cách có thể dự đoán được đối với các luồng khác đồng bộ trên cùng một màn hình. Sau khi chúng ta thoát khỏi một khối đồng bộ, chúng ta giải phóng màn hình, nó có tác dụng xả bộ nhớ đệm sang bộ nhớ chính, do đó các luồng được tạo bởi luồng này có thể được hiển thị cho các luồng khác. Trước khi chúng ta có thể nhập một khối đồng bộ, chúng ta thu được màn hình, có hiệu lực làm mất hiệu lực bộ nhớ cache của bộ xử lý cục bộ sao cho các biến sẽ được nạp lại từ bộ nhớ chính. Sau đó, chúng tôi sẽ có thể xem tất cả các bài viết được hiển thị bởi bản phát hành trước.

Do đó, giá trị của someoneTouchedMeWhenIWasWorking được xuất bản vào bộ nhớ chính khi một chuỗi thoát khỏi khối synchonized. Giá trị được xuất bản sau đó được đọc từ bộ nhớ chính khi nhập các khối synchronized.

Bên ngoài khối synchronized, giá trị có thể được lưu trong bộ nhớ cache. Nhưng khi nhập một khối đồng bộ, nó sẽ được đọc từ bộ nhớ chính, và nó sẽ được xuất bản khi thoát nó, mặc dù nó không được khai báo volatile.


Có một câu trả lời thú vị về câu hỏi này ở đây: What can force a non-volatile variable to be refreshed?

Tiếp theo là một cuộc nói chuyện ở đây: http://chat.stackoverflow.com/rooms/46867/discussion-between-gray-and-voo

Lưu ý rằng trong ví dụ cuối cùng, @SotiriosDelimanolis cố gắng làm gì để: synchronized(new Object()) {} trong khi (! dừng lại) chặn nhưng nó không dừng lại. Ông nói thêm rằng với synchronized(this), nó đã làm. Tôi nghĩ rằng hành vi này được giải thích trong FAQ khi nó khẳng định:

đồng bộ đảm bảo rằng bộ nhớ viết bởi một sợi trước hoặc trong một khối đồng bộ được thực hiện có thể nhìn thấy một cách có thể dự đoán cho chủ đề khác mà đồng bộ hóa trên màn hình cùng.

+0

Tôi vừa thử một ví dụ đơn giản. Tắt cụm từ ** hoặc trong một khối đã đồng bộ ** là rất quan trọng (đã bỏ qua nó :() .. Các giá trị được đọc từ bộ nhớ chính ngay cả * trong một khối được đồng bộ hóa * JLS là loại * ngoại giao * ở đây, * theo cách có thể dự đoán * khác với * luôn * * – TheLostMind

+0

Không nhập khối được đồng bộ luôn xóa các thay đổi được thực hiện bởi chủ đề này và tải lại mọi thứ từ chính? –

+1

Rõ ràng là có: * Trước khi chúng ta có thể nhập một khối đồng bộ, chúng ta có được màn hình, có tác dụng làm mất hiệu lực bộ nhớ cache của bộ xử lý cục bộ sao cho các biến sẽ được tải lại từ bộ nhớ chính * –

3

Những lý do JLS về xảy ra-trước mối quan hệ (hb). Hãy chú thích các đọc và viết thực hiện đề xuất của bạn, sử dụng các quy ước JLS:

T1: someoneTouchedMeWhenIWasWorking = false; // w 
T2: someoneTouchedMeWhenIWasWorking = true; // w' 
T1: if (someoneTouchedMeWhenIWasWorking)  // r 

Chúng tôi đã sau xảy ra-trước mối quan hệ:

  • hb (w, r), do quy tắc trật tự chương trình
  • hb (w', r), do synchronized keyword (An mở khóa trên một màn hình xảy ra-trước mỗi khóa tiếp theo trên màn hình đó): nếu chương trình của bạn thực hiện w ' trước r (nhìn vào đồng hồ của bạn), mà bạn đảm nhận, sau đó hb (w', r)

Câu hỏi của bạn cho dù r được phép đọc w. Này được đề cập đến trong the last section of #17.4.5:

Chúng ta nói rằng một đọc r của một biến v được phép quan sát một ghi w-v nếu, trong xảy ra-trước trật tự một phần việc thực hiện dấu vết:

  • r không ra lệnh trước khi w (i. e., nó không phải là trường hợp đó hb (r, w)), và
  • không có can thiệp ghi w'-v (ví dụ: không viết w ' đến v sao cho hb (w, w')hb (w ', r)).

Thức, một đọc r được phép xem kết quả của một ghi w nếu không có xảy ra-trước trật tự để ngăn chặn điều đó đọc.

  • Điều kiện đầu tiên được thực hiện rõ ràng: chúng tôi xác định rằng hb (w, r).
  • Điều kiện thứ hai cũng đúng vì không có xảy ra-trước mối quan hệ giữa ww'

Vì vậy, trừ khi tôi bị mất một cái gì đó, có vẻ như r thể về mặt lý thuyết quan sát hoặc w hoặc w '.

Trong thực tế, như đã chỉ ra bởi câu trả lời khác, các JVM điển hình được thực hiện theo một cách mà là nghiêm ngặt hơn rằng JMM và tôi không nghĩ rằng bất kỳ JVM hiện sẽ cho phép r để quan sát w, nhưng điều đó không phải là một sự đảm bảo.

Trong mọi trường hợp, sự cố trở nên dễ dàng để khắc phục, với volatile hoặc bằng cách bao gồm w trong khối synchronized.

+0

Sự khác biệt quan trọng giữa * xảy ra trước khi * lý luận và lý luận thời gian là thứ tự thời gian là tổng trong khi * xảy ra trước * là một phần. Vì vậy, nếu bạn khẳng định rằng * w '* chắc chắn đã xảy ra sau * w *, bạn đang giới thiệu nhiều thông tin hơn so với những gì tồn tại về * xảy ra trước *. Theo như * xảy ra trước * liên quan, * w * và * w '* không có thứ tự giữa chúng. –

+0

Tuy nhiên, trong thực tế, bạn sẽ thực tế có khá nhiều rắc rối khi xác định thứ tự thời gian trong bất kỳ thực thi thực sự nào. Đặc biệt là khi bạn xem xét rằng "viết" không phải là một điểm lý tưởng của thời gian; nó là một tiến trình * đang diễn ra trong CPU. Hoạt động ghi đang dần lan truyền thông qua các bộ đệm cửa hàng, L1, L2, và như vậy, và các ghi khác từ các lõi khác đang làm cùng một lúc. Ai sẽ nói cái nào đến trước? Bạn sớm kết thúc nói về các thuật ngữ cụ thể của CPU và ảo tưởng về một trật tự thời gian nhất định bốc hơi. –

+1

... đó là chính xác lý do tại sao * xảy ra trước * được định nghĩa là nó --- nếu không nó sẽ là bên cạnh không thể đáp ứng các gurantees JMM trong khi duy trì hiệu suất hàng đầu. –

3

Với mô hình bộ nhớ Java, có bao giờ có thể tiến trình chạy luồng sẽ thấy hiệu quả của ghi cục bộ, không đồng bộ, ngay cả khi chạm xảy ra sau?

Tôi thấy một sự đơn giản hóa quan trọng nhưng quan trọng đang diễn ra ở đây: làm thế nào bạn nghĩ rằng bạn sẽ xác định rằng hành động viết bởi chuỗi thứ hai xảy ra chính xác giữa hành động viết và đọc của chuỗi đầu tiên? Để có thể nói rằng, bạn sẽ cần phải giả định rằng một hành động ghi là một điểm lý tưởng trên dòng thời gian và tất cả các ghi xảy ra theo thứ tự tuần tự. Trên phần cứng thực tế, viết là một quá trình rất phức tạp liên quan đến đường ống CPU, bộ đệm lưu trữ, bộ nhớ cache L1, bộ đệm L2, bus phía trước và cuối cùng là RAM. Cùng một loại quá trình đi trên đồng thời trên tất cả các lõi CPU. Vì vậy, chính xác những gì bạn có nghĩa là bởi một trong những viết "xảy ra sau khi" khác?

Hơn nữa, xem xét các "Roach Motel" mô, mà dường như để giúp nhiều người như là một loại của một "shortcut tinh thần" vào hậu quả của Memory Model Java: mã của bạn một cách hợp pháp có thể được chuyển đổi thành

public void process() { 
    doStuff(); 
    synchronized(this) { 
     someoneTouchedMeWhenIWasWorking = false; 
     if (someoneTouchedMeWhenIWasWorking) { 
      System.out.println("Hey!"); 
     } 
    } 
} 

này là một cách tiếp cận hoàn toàn khác để khai thác các quyền tự do được cung cấp bởi Mô hình bộ nhớ Java để đạt được hiệu suất. Việc đọc bên trong mệnh đề if thực sự sẽ yêu cầu đọc địa chỉ bộ nhớ thực, trái ngược với giá trị của false trực tiếp và do đó xóa toàn bộ if-block (điều này thực sự xảy ra mà không cần synchronized), nhưng không nhất thiết phải viết false ghi vào RAM. Trong bước tiếp theo của việc giải thích trình biên dịch tối ưu hóa có thể quyết định xóa nhiệm vụ sang false hoàn toàn. Tùy thuộc vào chi tiết cụ thể của tất cả các phần khác của mã, đây là một trong những điều có thể xảy ra, nhưng cũng có nhiều khả năng khác.

Thông điệp chính cần lấy đi từ bên trên phải là: đừng giả vờ bạn có thể lý do trên mã Java bằng cách sử dụng một số khái niệm "hiệu lực bộ nhớ cache cục bộ" đơn giản hóa; thay vào đó hãy gắn vào Mô hình bộ nhớ Java chính thức và xem xét tất cả các quyền tự do mà nó cung cấp thực sự.

Có cần thiết phải đồng bộ hóa ghi đầu tiên hoặc biến biến động không?

Với thảo luận ở trên, tôi hy vọng bạn nhận ra rằng câu hỏi này thực sự không có chất. Bạn sẽ quan sát việc viết bởi chủ đề khác và do đó được đảm bảo tuân thủ tất cả các hành động khác trước khi viết, hoặc bạn sẽ không quan sát nó và sẽ không có bảo đảm. Nếu mã của bạn không có các cuộc đua dữ liệu, kết quả sẽ phù hợp với bạn. Nếu nó có các cuộc đua dữ liệu, sau đó sửa chữa tốt hơn thay vì cố gắng sửa chữa thành ngữ của bạn.

Cuối cùng, hãy tưởng tượng biến của bạn biến động. Điều gì sẽ cho bạn rằng bạn không có bây giờ? Về mặt hình thức JMM, hai lệnh viết sẽ có thứ tự xác định giữa chúng (áp đặt theo thứ tự đồng bộ), nhưng bạn sẽ ở chính xác cùng vị trí như bạn hiện tại: thứ tự xác định sẽ là tùy ý chấp hành.

Các vấn đề liên quan