2015-07-04 22 views
12

Tôi hiện đang đọc JSR-133 (Mô hình bộ nhớ Java) và tôi không hiểu tại sao f.y có thể không được khởi tạo (có thể thấy 0). Ai đó có thể giải thích cho tôi?Khởi tạo trường phi cuối cùng

class FinalFieldExample { 
    final int x; 
    int y; 
    static FinalFieldExample f; 

    public FinalFieldExample() { 
     x = 3; 
     y = 4; 
    } 

    static void writer() { 
     f = new FinalFieldExample(); 
    } 

    static void reader() { 
     if (f != null) { 
      int i = f.x; // guaranteed to see 3 
      int j = f.y; // could see 0 
     } 
    } 
} 
+0

Tôi biết điều này liên quan đến JVM được phép sắp xếp lại các bài viết, nhưng tôi không biết chi tiết. : - | –

+0

Khi tôi nghĩ rằng bạn chưa đọc jsr133 đúng cách, điều đó được đề cập rõ ràng ở đó.'Không viết một tham chiếu đến đối tượng đang được xây dựng ở một nơi khác có thể thấy chủ đề 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, khi đối tượng được xem bởi một chuỗi khác, chuỗi đó sẽ luôn thấy phiên bản được tạo đúng của trường cuối cùng của đối tượng' nhưng đối với chuỗi trường không cuối cùng có thể thấy giá trị mặc định. – Prashant

+0

vâng, điều này thật điên rồ. hy vọng họ sẽ sửa nó trong java9. – ZhongYu

Trả lời

-1

mô hình bộ nhớ Java cho phép một sợi để tạo ra một FinalFieldExample, khởi thức x và lưu một tham chiếu đến FinalFieldExample dụ để f trường trước khi khởi tạo phi chính thức y.

+0

Điều gì xảy ra nếu trường 'x' được khởi tạo sau' y' trong hàm tạo? –

+0

Chỉ dẫn sắp xếp lại được cho phép, đây cũng là lý do tại sao Java có thể xuất bản các trường hợp uninitiazlied –

+0

Câu trả lời này không hữu ích nếu không có trích dẫn, các trích dẫn có liên quan và giải thích thêm. Không có thông tin trong câu trả lời không có trong câu hỏi. –

7

Đây được gọi là hiệu ứng "xuất bản sớm".

Đặt đơn giản, JVM được phép sắp xếp lại hướng dẫn chương trình (vì lý do hiệu suất), nếu sắp xếp lại không vi phạm các hạn chế của JMM.

Bạn mong đợi mã f = new FinalFieldExample(); để chạy như:

1. tạo ra các thể hiện của FinalFieldExample
2. assign 3 đến x
3. assign 4 đến y
4. assign tạo đối tượng để biến f

Nhưng trong mã được cung cấp, không có gì có thể ngăn chặn JVM từ hướng dẫn sắp xếp lại, vì vậy nó có thể chạy các mã như:

1. tạo ra các thể hiện của FinalFieldExample
2. assign 3 đến x
3. gán thô, không được khởi tạo đầy đủ đối tượng để biến f
4. assign 4 đến y

Nếu sắp xếp lại xảy ra trong một đơn môi trường chủ đề, chúng tôi thậm chí sẽ không nhận thấy nó. Đó là bởi vì chúng ta mong đợi, các đối tượng đó sẽ được tạo ra hoàn toàn trước khi chúng ta bắt đầu làm việc với chúng và JVM tôn trọng những mong đợi của chúng ta. Bây giờ, những gì có thể xảy ra, nếu một số chủ đề chạy mã này cùng một lúc? Trong ví dụ tiếp theo thread1 đang thực hiện phương pháp writer() và thread2 - Phương pháp reader():

Chủ đề 1: tạo ra các thể hiện của FinalFieldExample
Chủ đề 1: Sử dụng từ 3 đến x
Chủ đề 1: gán liệu, đối tượng khởi tạo không đầy đủ để biến f
chủ đề 2: đọc f, nó không phải là null
chủ đề 2: đọc fx, nó được 3
chủ đề 2: đọc fy, nó vẫn là 0
chủ đề 1: Sử dụng 4 đến y

Chắc chắn là không tốt. Để ngăn JVM làm điều này, chúng ta cần cung cấp thêm thông tin về chương trình. Đối với ví dụ cụ thể này, có một số cách để khắc phục tính nhất quán của bộ nhớ:

  • khai báo yfinal biến. Điều này sẽ gây ra hiệu ứng "freeze". Tóm lại, các biến cuối cùng sẽ luôn được khởi tạo vào thời điểm bạn truy cập chúng, nếu tham chiếu đến đối tượng không bị rò rỉ trong khi xây dựng.
  • khai báo fvolatile biến.Thao tác này sẽ tạo "synchronization order" và khắc phục sự cố. Nói tóm lại, các hướng dẫn không thể được sắp xếp lại dưới dạng ghi dễ bay hơi và đọc ở trên dễ bay hơi. Gán cho biến số f là một ghi dễ bay hơi, có nghĩa là không thể sắp xếp lại và thực thi các lệnh sau khi gán. Đọc từ biến số f là đọc dễ đọc, vì vậy, không thể thực thi đọc f.x trước khi đọc. Sự kết hợp của v-write và v-read được gọi là thứ tự đồng bộ hóa và cung cấp độ nhất quán bộ nhớ mong muốn.

Here là một blog hay, có thể trả lời tất cả câu hỏi của bạn về JMM.

+0

Nếu cuộc gọi đến hàm tạo có thể xảy ra sau khi tham chiếu đến đối tượng được xuất bản thành 'f', thì JVM đáp ứng _guaranteed như thế nào để xem 3_? –

+0

@SotiriosDelimanolis này được gọi là "đóng băng hành động". Tóm lại, nếu có một trường cuối cùng gán bên trong một hàm tạo, JMM buộc phải thực hiện nó trước khi tham chiếu đến đối tượng mới sẽ được công bố. – AdamSkywalker

+0

Nếu chúng ta có 'y = 4' trước khi gán cho trường' final' 'x', hành động đóng băng này có đảm bảo rằng' y' cũng được khởi tạo không? Hoặc JVM có thể sắp xếp lại nó sau khi gán 'x' và xuất bản đối tượng, để' y' có khả năng chưa được khởi tạo? –

1

JVM có thể sắp xếp lại bộ nhớ đọc và ghi, do đó, tham chiếu đến f có thể được ghi vào bộ nhớ chính trước giá trị f.y. Nếu một chuỗi khác đọc f.y giữa hai lần viết này, nó sẽ đọc 0. Tuy nhiên, nếu bạn tạo một rào cản bộ nhớ bằng cách viết một trường final hoặc volatile, đọc và viết sau khi rào cản không thể được sắp xếp lại đối với lần đọc và viết trước hàng rào. Vì vậy, nó đảm bảo rằng cả hai ff.y sẽ được viết trước khi một chủ đề khác đọc f.

Tôi đã hỏi một câu hỏi tương tự here. Các câu trả lời đi vào chi tiết hơn rất nhiều.

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