2013-04-27 48 views
31

Trong khi đọc tài liệu Java về lỗi nhất quán của bộ nhớ. Tôi tìm ra những điểm liên quan đến hai hành động đó tạo ra xảy ra - trước khi mối quan hệ:Tính nhất quán của bộ nhớ - xảy ra trước mối quan hệ trong Java

  • Khi một tuyên bố gọi Thread.start(), mỗi tuyên bố rằng có một xảy ra-trước khi mối quan hệ với tuyên bố rằng cũng có một xảy ra-trước khi mối quan hệ với tất cả các tuyên bố được thực hiện bởi luồng mới . Hiệu ứng của mã dẫn đến việc tạo chủ đề mới được hiển thị cho chuỗi mới.

  • Khi một thread chấm dứt và gây ra một Thread.join() trong một thread trở lại, sau đó tất cả các báo cáo thực hiện bởi các chấm dứt
    chủ đề có xảy ra-trước khi mối quan hệ với tất cả những điều khoản
    sau thành công tham gia. Các hiệu ứng của mã trong luồng hiện có thể hiển thị với chuỗi đã thực hiện phép nối.

Tôi không hiểu ý nghĩa của chúng. Sẽ rất tuyệt nếu ai đó giải thích nó bằng một ví dụ đơn giản.

+5

'mối quan hệ xảy ra trước đó' có nghĩa là các tập hợp các câu lệnh đó được đảm bảo thực thi trước một bộ câu lệnh khác. Vì vậy, trong kịch bản đầu tiên .. các câu lệnh dẫn đến việc bắt đầu một luồng mới có mối quan hệ xảy ra trước đó với các câu lệnh sẽ được thực hiện bởi luồng mới được bắt đầu. Bất kỳ thay đổi nào được thực hiện bởi các câu lệnh đó sẽ được hiển thị cho các câu lệnh được thực hiện bởi luồng. –

+1

Tôi thấy trang này hữu ích: http://preshing.com/20130702/the-happens-before-relation/ Nó đưa ra ví dụ về mối quan hệ "xảy ra-trước" giữa A và B khác với A thực sự xảy ra trước B. –

Trả lời

26

CPU hiện đại không phải lúc nào cũng ghi dữ liệu vào bộ nhớ theo thứ tự được cập nhật, ví dụ nếu bạn chạy mã giả (các biến giả định luôn được lưu vào bộ nhớ tại đây để đơn giản);

a = 1 
b = a + 1 

... CPU có thể rất tốt viết b vào bộ nhớ trước khi nó viết a vào bộ nhớ. Đây không thực sự là một vấn đề miễn là bạn chạy mọi thứ trong một chuỗi đơn lẻ, vì chuỗi chạy mã ở trên sẽ không bao giờ thấy giá trị cũ của biến số một khi các bài tập đã được thực hiện.

Đa luồng là một vấn đề khác, bạn nghĩ rằng đoạn mã sau sẽ cho phép một chuỗi khác nhận giá trị của phép tính nặng của bạn;

a = heavy_computation() 
b = DONE 

... thread khác làm ...

repeat while b != DONE 
    nothing 

result = a 

Vấn đề mặc dù được rằng lá cờ thực hiện có thể được thiết lập trong bộ nhớ trước khi kết quả được lưu trữ vào bộ nhớ, vì vậy thread khác có thể nhận giá trị của địa chỉ bộ nhớ trước khi kết quả tính toán được ghi vào bộ nhớ.

Vấn đề tương tự sẽ - nếu Thread.startThread.join không có một "xảy ra trước khi" đảm bảo - cung cấp cho bạn các vấn đề với mã tương tự;

a = 1 
Thread.start newthread 
... 

newthread: 
    do_computation(a) 

... kể từ a có thể không có giá trị được lưu vào bộ nhớ khi chuỗi bắt đầu.

Vì bạn hầu như luôn luôn muốn thread mới để có thể sử dụng dữ liệu bạn khởi tạo trước khi bắt đầu nó, Thread.start có một "xảy ra trước khi" bảo lãnh, có nghĩa là, dữ liệu đã được cập nhật trước khi gọi Thread.start được đảm bảo có sẵn cho chủ đề mới.Điều tương tự cũng xảy ra cho Thread.join trong đó dữ liệu được viết bởi chuỗi mới được đảm bảo hiển thị cho chuỗi nối sau khi kết thúc.

Nó chỉ làm cho luồng dễ dàng hơn nhiều.

7

Sự cố hiển thị chủ đề có thể xảy ra trong mã không được đồng bộ hóa chính xác theo mô hình bộ nhớ java. Do trình biên dịch & tối ưu hóa phần cứng, được viết bởi một luồng không phải lúc nào cũng hiển thị bằng các lần đọc của một chuỗi khác. Mô hình bộ nhớ Java là một mô hình chính thức làm cho các quy tắc "được đồng bộ hóa" rõ ràng, để các lập trình viên có thể tránh được các vấn đề về hiển thị luồng.

Xảy ra trước là mối quan hệ được xác định trong mô hình đó và nó đề cập đến các thực thi cụ thể. Một chữ W được chứng minh là xảy ra trước khi đọc R được đảm bảo hiển thị bằng đọc đó, giả sử không có cách viết nào khác (nghĩa là một chữ không có quan hệ xảy ra trước khi đọc hoặc xảy ra giữa chúng theo quan hệ đó).

Loại đơn giản nhất xảy ra trước khi mối quan hệ xảy ra giữa các hành động trong cùng một chuỗi. Một ghi W đến V trong chuỗi P xảy ra trước khi đọc R của V trong cùng một luồng, giả định rằng W đến trước R theo thứ tự chương trình.

Văn bản bạn đang đề cập đến cho biết rằng thread.start() và thread.join() cũng đảm bảo mối quan hệ xảy ra trước đó. Bất kỳ hành động nào xảy ra trước khi thread.start() cũng xảy ra trước bất kỳ hành động nào trong chuỗi đó. Tương tự, các hành động trong chuỗi diễn ra trước bất kỳ hành động nào xuất hiện sau thread.join().

Ý nghĩa thực tế của điều đó là gì? Nếu ví dụ bạn bắt đầu một chuỗi và chờ cho nó chấm dứt theo cách không an toàn (ví dụ: ngủ trong một thời gian dài hoặc thử nghiệm một số cờ không đồng bộ), thì khi bạn thử đọc các sửa đổi dữ liệu do thread, bạn có thể thấy chúng một phần, do đó có nguy cơ mâu thuẫn dữ liệu. Phương thức join() hoạt động như một rào chắn đảm bảo rằng bất kỳ phần dữ liệu nào được xuất bản bởi luồng này đều có thể nhìn thấy hoàn toàn và nhất quán bởi luồng khác.

18

Hãy xem xét điều này:

static int x = 0; 

public static void main(String[] args) { 
    x = 1; 
    Thread t = new Thread() { 
     public void run() { 
      int y = x; 
     }; 
    }; 
    t.start(); 
} 

Các chủ đề chính đã thay đổi lĩnh vực x. Mô hình bộ nhớ Java không đảm bảo rằng thay đổi này sẽ được hiển thị cho các chủ đề khác nếu chúng không được đồng bộ hóa với chuỗi chính. Nhưng chuỗi t sẽ thấy thay đổi này vì chủ đề chính được gọi là t.start() và JLS đảm bảo rằng việc gọi t.start() làm cho thay đổi thành x hiển thị trong t.run() vì vậy y được đảm bảo được chỉ định 1.

Cùng quan tâm Thread.join();

+0

Vâng, tôi đồng ý, đã thay đổi câu trả lời một chút để tránh bị viêm phổi, hãy nhìn vào nó ngay bây giờ –

1

Theo tài liệu oracle, họ xác định mối quan hệ đó xảy ra Các-trước khi chỉ đơn giản là một sự đảm bảo rằng nhớ viết bởi một tuyên bố cụ thể là có thể nhìn thấy khác tuyên bố cụ thể.

package happen.before; 

public class HappenBeforeRelationship { 


    private static int counter = 0; 

    private static void threadPrintMessage(String msg){ 
     System.out.printf("[Thread %s] %s\n", Thread.currentThread().getName(), msg); 
    } 

    public static void main(String[] args) { 

     threadPrintMessage("Increase counter: " + ++counter); 
     Thread t = new Thread(new CounterRunnable()); 
     t.start(); 
     try { 
      t.join(); 
     } catch (InterruptedException e) { 
      threadPrintMessage("Counter is interrupted"); 
     } 
     threadPrintMessage("Finish count: " + counter); 
    } 

    private static class CounterRunnable implements Runnable { 

     @Override 
     public void run() { 
      threadPrintMessage("start count: " + counter); 
      counter++; 
      threadPrintMessage("stop count: " + counter); 
     } 

    } 
} 

Output sẽ là:

[Thread main] Increase counter: 1 
[Thread Thread-0] start count: 1 
[Thread Thread-0] stop count: 2 
[Thread main] Finish count: 2 

Có một đầu ra nhìn, dòng [Chủ đề Chủ đề-0] bắt đầu đếm: 1 cho thấy rằng tất cả những thay đổi truy cập trước khi gọi trình Thread.start() là hiển thị trong phần thân của chủ đề.

Và dòng [Chủ đề chính] Số kết thúc: 2 cho biết rằng tất cả thay đổi trong nội dung của chuỗi đều hiển thị với chuỗi chính gọi Thread.join().

Hy vọng nó có thể giúp bạn rõ ràng.

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