2017-10-09 23 views
7

Đọc thông số kỹ thuật ngôn ngữ Java, tôi thấy đoạn trích này về lĩnh vực thức:Nếu bạn gán một đối tượng cho trường cuối cùng, các luồng khác sẽ thấy các bản cập nhật trước đó của các trường không phải là cuối cùng/không dễ bay hơi của đối tượng đó không?

Mô hình sử dụng cho các lĩnh vực cuối cùng là một đơn giản: Đặt các trường thức cho một đối tượng trong constructor của đối tượng đó; và không viết một tham chiếu cho đối tượng đang được xây dựng ở một nơi mà một chuỗi khác có thể nhìn thấy nó trước khi hàm tạo của đối tượng kết thúc. Nếu điều này được theo sau, sau đó khi đối tượng được xem bởi một chuỗi khác, thì chuỗi sẽ luôn thấy phiên bản được xây dựng chính xác của các trường cuối cùng của đối tượng đó . Nó cũng sẽ thấy các phiên bản của bất kỳ đối tượng nào hoặc các mảng được tham chiếu bởi các trường cuối cùng đó ít nhất là cập nhật làm trường cuối cùng là.

Link: https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5

Câu hỏi của tôi là, làm "phiên bản" Cập nhật trung bình? Điều này có nghĩa là các trường không cuối cùng/không bay hơi của một đối tượng được tham chiếu bởi một trường cuối cùng cũng sẽ được đọc từ bộ nhớ chính (không phải bộ đệm cục bộ) sau khi xây dựng?


Ví dụ

Vì vậy, chúng ta hãy nói thread #1 tạo ra một objectB và đặt một trong phi chính thức/lĩnh vực non-volatile của nó.

Sau đó thread #2 bộ cùng lĩnh vực để một cái gì đó khác nhau, tạo ra một số khác objectA với một lĩnh vực thức thiết lập như objectB, sau đó đặt mà objectA nơi nào đó mà thread #1 có thể lấy nó.

thread #1 sau đó nhận số objectA và xem trường cuối cùng là objectB. Có thể cho thread #1 để không thấy các thay đổi đối với objectB được thực hiện bởi thread #2?

Hoặc đây là một số mã hiển thị những gì tôi muốn nói:

public class Test { 
    private static final ConcurrentLinkedQueue<A> myAs = new ConcurrentLinkedQueue<>(); 
    private static long timer = System.nanoTime() + 3000000000L; // 3 seconds into the future 

    public static void main(String... args) { 
     B myB = new B("thread #1"); // Set in thread 1 

     new Thread(() -> { 
      myB.setString("thread #2"); // Set in thread 2 
      myAs.add(new A(myB)); 
     }).start(); 

     for(long i = 0; i < x; i = System.nanoTime()) {} // Busy-wait for about 3 seconds 

     System.out.println(myAs.poll().getB().getString()); // Print out value 
    } 

    public static class A { 
     private final B b; 

     public A(B b) { 
      this.b = b; 
     } 

     public B getB() { 
      return b; 
     } 
    } 

    public static class B { 
     private String s = null; 

     public B(String s) { 
      this.s = s; 
     } 

     public String getString() { 
      return s; 
     } 

     public void setString(String s) { 
      this.s = s; 
     } 
    } 
} 

Mã này dường như để đọc các giá trị cập nhật, nhưng tôi không chắc chắn nếu đó là chỉ cần ra khỏi ngẫu nhiên may mắn.

+3

Khá tốt cho câu hỏi đầu tiên. – lexicore

Trả lời

1

"Có thể cho thread #1 để không thấy thay đổi đối với objectB được thực hiện bởi thread #2?"

Có. Bởi vì thread #1 có thể cache giá trị.

Trích dẫn từ số liệu có nghĩa là có happened before giữa việc gán giá trị trường cuối cùng và xuất bản đối tượng.

1

này là thú vị và tôi nghĩ rằng tôi đã thực sự đọc về nó một lúc trước ... Dưới đây là một ví dụ (nếu tôi nhớ chính xác ví dụ):

static class Holder { 

    private final String[] names; 

    static Holder newHolder; 

    public Holder() { 
     super(); 
     names = new String[3]; 
     names[0] = "first"; 
     names[1] = "last"; 
    } 

    public void newObject() { 
     newHolder = new Holder(); 
     newHolder.names[2] = "oneMore"; 
    } 

    public void readObject() { 
     System.out.println(Arrays.toString(newHolder.names)); 
    } 

} 

Giả sử bạn có hai chủ đề liên quan ở đây : ThreadAThreadB.Và bây giờ cũng giả sử rằng ThreadA gọi newObject; khi nó được thực hiện ThreadB gọi readObject.

Hoàn toàn không đảm bảo rằng ThreadB sẽ in first, last, oneMore; nó chỉ đảm bảo rằng firstlast sẽ có mặt để chắc chắn.

Btw này hoàn toàn dễ hiểu khi bạn nghĩ về mặt số MemoryBarriers được sử dụng trong trường hợp final fields used inside constructor.

Dưới sự thực hiện hiện nay, điều này thực sự trông như thế này:

public Holder() { 
    super(); 
    names = new String[3]; 
    names[0] = "first"; 
    names[1] = "last"; 
} 
// [StoreStore] 
// [LoadStore] 

có hai rào cản được chèn vào cuối những constrcutor mà ngăn chặn khác đọc và cửa hàng xảy ra.

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