2011-02-08 33 views
68

Giáo viên của tôi trong một lớp học cấp trên java về luồng nói điều gì đó mà tôi không chắc chắn.Biến tĩnh có được chia sẻ giữa các luồng không?

Ông tuyên bố rằng mã sau sẽ không nhất thiết phải cập nhật biến số ready. Theo ông, hai chủ đề không nhất thiết phải chia sẻ biến tĩnh, đặc biệt trong trường hợp mỗi luồng (thread chính so với ReaderThread) đang chạy trên bộ xử lý riêng của nó và do đó không chia sẻ cùng một thanh ghi/bộ nhớ cache/v.v ... CPU sẽ không cập nhật khác.

Về cơ bản, ông cho biết có thể ready được cập nhật trong chuỗi chính, nhưng KHÔNG ở trong ReaderThread, do đó ReaderThread sẽ lặp vô hạn. Ông cũng tuyên bố có thể chương trình in '0' hoặc '42'. Tôi hiểu làm thế nào '42' có thể được in, nhưng không phải là '0'. Ông đã đề cập này sẽ là trường hợp khi biến số number được đặt thành giá trị mặc định.

Tôi nghĩ có lẽ nó không được đảm bảo rằng biến tĩnh được cập nhật giữa các luồng, nhưng điều này đánh tôi rất kỳ quặc đối với Java. Việc thực hiện ready có thể khắc phục được sự cố này không?

Ông đã cho thấy mã này:

public class NoVisibility { 
    private static boolean ready; 
    private static int number; 
    private static class ReaderThread extends Thread { 
     public void run() { 
      while (!ready) Thread.yield(); 
      System.out.println(number); 
     } 
    } 
    public static void main(String[] args) { 
     new ReaderThread().start(); 
     number = 42; 
     ready = true; 
    } 
}
+0

Khả năng hiển thị của các biến không phải cục bộ không phụ thuộc vào chúng là biến tĩnh, trường đối tượng hay phần tử mảng, tất cả đều có cùng cân nhắc. (Với vấn đề mà các phần tử mảng không thể biến động được.) –

+1

hỏi giáo viên của bạn loại kiến ​​trúc nào mà anh cho là có thể thấy '0'. Tuy nhiên, về lý thuyết anh ấy đúng. – bestsss

+2

@bestsss Yêu cầu loại câu hỏi đó sẽ tiết lộ cho giáo viên rằng anh đã bỏ lỡ toàn bộ quan điểm của những gì anh đang nói. Vấn đề là các lập trình viên có thẩm quyền hiểu những gì được bảo đảm và những gì không và không dựa vào những thứ không được bảo đảm, ít nhất là không hiểu chính xác những gì không được bảo đảm và tại sao. –

Trả lời

53

Không có vấn đề về khả năng hiển thị cụ thể đối với các biến tĩnh. Có vấn đề về mức độ hiển thị được áp đặt bởi mô hình bộ nhớ của JVM. Here's an article talking about the memory model and how writes become visible to threads. Bạn không thể dựa vào những thay đổi mà một sợi chỉ hiển thị cho các chủ đề khác một cách kịp thời (thực ra JVM không có nghĩa vụ thực hiện những thay đổi đó cho bạn), trừ khi bạn thiết lập một số happens-before relationship, đây là câu trích dẫn từ liên kết đó (được cung cấp trong phần bình luận của Jed Wesley-Smith):

Chương 17 của Đặc tả Ngôn ngữ Java xác định mối quan hệ xảy ra trước đó trong các hoạt động bộ nhớ như đọc và ghi biến chia sẻ. Kết quả của việc viết bởi một luồng được đảm bảo chỉ hiển thị cho một chuỗi khác đọc nếu thao tác ghi xảy ra trước khi thao tác đọc. Các cấu trúc đồng bộ và dễ bay hơi, cũng như các phương thức Thread.start() và Thread.join(), có thể hình thành các mối quan hệ xảy ra trước khi xảy ra. Cụ thể:

  • Mỗi hành động trong một chủ đề xảy ra trước mỗi hành động trong chuỗi đó đến sau theo thứ tự của chương trình.

  • Mở khóa (khối đồng bộ hoặc lối thoát phương thức) của màn hình xảy ra trước mỗi khóa tiếp theo (khối đồng bộ hoặc mục nhập phương thức) của cùng một màn hình đó. Và bởi vì mối quan hệ xảy ra trước đó là transitive, tất cả các hành động của một luồng trước khi mở khóa xảy ra trước khi tất cả các hành động tiếp theo với bất kỳ luồng nào khóa màn hình đó.

  • Việc ghi vào trường dễ bay hơi xảy ra trước mỗi lần đọc tiếp theo của cùng trường đó. Viết và đọc của các lĩnh vực dễ bay hơi có tác dụng nhất quán bộ nhớ tương tự như vào và thoát khỏi màn hình, nhưng không kéo theo khóa loại trừ lẫn nhau.

  • Cuộc gọi để bắt đầu một chuỗi xảy ra trước khi bất kỳ hành động nào trong chuỗi bắt đầu.

  • Tất cả các hành động trong chuỗi đều xảy ra trước khi bất kỳ chuỗi nào khác trả về thành công từ việc tham gia vào chuỗi đó.

+3

Trong thực tế, "một cách kịp thời" và "bao giờ" là đồng nghĩa. Nó sẽ rất có thể cho các mã trên không bao giờ chấm dứt. – TREE

+2

Ngoài ra, điều này thể hiện một mô hình chống khác. Không sử dụng dễ bay hơi để bảo vệ nhiều hơn một phần của trạng thái chia sẻ. Ở đây, số và sẵn sàng là hai phần của nhà nước, và để cập nhật/đọc chúng cả hai đều, bạn cần đồng bộ hóa thực tế. – TREE

+0

Đừng quên 'final'. biến cuối cùng luôn hiển thị cho tất cả các chuỗi. –

4

Trong vòng một classloader duy nhất, lĩnh vực tĩnh luôn được chia sẻ. Để quy mô dữ liệu một cách rõ ràng cho các chủ đề, bạn muốn sử dụng một cơ sở như ThreadLocal.

8

Về cơ bản, nhưng thực tế vấn đề phức tạp hơn. Khả năng hiển thị của dữ liệu được chia sẻ có thể bị ảnh hưởng không chỉ bởi bộ đệm CPU mà còn bị ảnh hưởng bởi việc thực hiện không đúng hướng dẫn.

Vì vậy, Java định nghĩa một Memory Model, nêu rõ các tình huống nào có thể thấy trạng thái nhất quán của dữ liệu được chia sẻ.

Trong trường hợp cụ thể của bạn, việc thêm volatile đảm bảo khả năng hiển thị.

4

Chúng được chia sẻ tất nhiên theo nghĩa là cả hai đều tham chiếu đến cùng một biến, nhưng chúng không nhất thiết phải xem nội dung cập nhật của nhau. Điều này đúng cho bất kỳ biến nào, không chỉ là tĩnh. Và theo lý thuyết, việc viết bởi một luồng khác có thể xuất hiện theo một thứ tự khác, trừ khi các biến được khai báo volatile hoặc các ghi được đồng bộ hóa một cách rõ ràng.

26

Anh ấy đang nói về mức độ hiển thị và không được thực hiện quá theo nghĩa đen.

Biến tĩnh thực sự được chia sẻ giữa các luồng, nhưng các thay đổi được thực hiện trong một chuỗi có thể không hiển thị với chuỗi khác ngay lập tức, làm cho nó có vẻ như có hai bản sao của biến.

Bài viết này trình bày một cái nhìn nhất quán với cách ông trình bày các thông tin:

Trước tiên, bạn phải hiểu một chút gì đó về mô hình bộ nhớ Java. Tôi đã vật lộn một chút trong những năm qua để giải thích nó một thời gian ngắn và tốt. Tính đến hôm nay, cách tốt nhất tôi có thể nghĩ ra để mô tả nó là nếu bạn tưởng tượng nó theo cách này:

  • Mỗi thread trong Java diễn ra trong một không gian bộ nhớ riêng biệt (điều này rõ ràng là không đúng sự thật, vì vậy chịu với tôi trên cái này).

  • Bạn cần phải sử dụng các cơ chế đặc biệt để đảm bảo rằng liên lạc xảy ra giữa các chủ đề này, giống như trên hệ thống chuyển thư.

  • Ghi nhớ xảy ra trong một chuỗi có thể "bị rò rỉ" và được xem bằng một chuỗi khác, nhưng điều này không được đảm bảo. Nếu không có thông tin liên lạc rõ ràng, bạn không thể đảm bảo các bài viết nào được xem bởi các chủ đề khác, hoặc thậm chí thứ tự mà chúng được nhìn thấy.

...

thread model

Nhưng một lần nữa, điều này chỉ đơn giản là một mô hình về tinh thần để suy nghĩ về luồng và không ổn định, không theo nghĩa đen như thế nào JVM hoạt động.

-2

@dontocsata bạn có thể quay trở lại để giáo viên và học ông của bạn một chút :)

vài lưu ý từ thế giới thực và bất kể những gì bạn nhìn thấy hoặc được thông báo. Xin lưu ý, các từ bên dưới liên quan đến trường hợp cụ thể này theo thứ tự chính xác được hiển thị.

Biến sau đây sẽ nằm trên cùng một dòng bộ nhớ cache trong hầu hết mọi kiến ​​trúc.

private static boolean ready; 
private static int number; 

Thread.exit (chủ đề chính) được đảm bảo để thoát và exit đảm bảo sẽ gây ra một hàng rào bộ nhớ, do nhóm chủ đề loại bỏ chủ đề (và nhiều vấn đề khác). (đó là một cuộc gọi được đồng bộ hóa, và tôi thấy không có cách nào để thực hiện w/o phần đồng bộ kể từ khi ThreadGroup cũng phải kết thúc nếu không có chủ đề daemon nào được để lại, vv).

Chủ đề bắt đầu ReaderThread sẽ giữ cho quá trình này hoạt động vì nó không phải là trình daemon! Do đó readynumber sẽ được xả cùng nhau (hoặc số trước khi chuyển đổi ngữ cảnh xảy ra) và không có lý do thực sự để sắp xếp lại trong trường hợp này ít nhất tôi thậm chí không thể nghĩ ra. Bạn sẽ cần một cái gì đó thực sự kỳ lạ để xem bất cứ điều gì nhưng 42. Một lần nữa tôi giả sử cả hai biến tĩnh sẽ nằm trong cùng một dòng bộ nhớ cache. Tôi chỉ không thể tưởng tượng một dòng bộ nhớ cache dài 4 byte HOẶC một JVM sẽ không gán chúng trong một khu vực liên tục (dòng bộ đệm).

+3

@bestsss trong khi đó là tất cả sự thật ngày nay, nó dựa trên việc triển khai JVM hiện tại và kiến ​​trúc phần cứng cho sự thật của nó, không phải trên ngữ nghĩa của chương trình. Điều này có nghĩa là chương trình vẫn bị hỏng, mặc dù chương trình có thể hoạt động. Nó rất dễ dàng để tìm một biến thể tầm thường của ví dụ này mà thực sự thất bại trong những cách quy định. –

+1

Tôi đã nói rằng nó không tuân theo spec, tuy nhiên như một giáo viên ít nhất tìm thấy một ví dụ phù hợp mà thực sự có thể thất bại trên một số kiến ​​trúc hàng hóa, vì vậy ví dụ này là loại thực. – bestsss

+0

lưu ý phụ: nhắc tôi cách 'java.util.concurrent.Exchanger' cố gắng tránh' cách ly giả' (cần một liên kết, tôi biết) bằng cách chạm vào dòng bộ nhớ cache bằng cách thêm 15 thời gian chờ thêm. – bestsss

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