Câu hỏi Biến tĩnh có được chia sẻ giữa các luồng không?


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.

Ông nói rằng đoạn mã sau sẽ không nhất thiết phải cập nhật ready biến. 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, anh ta nói có thể là 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 điều này sẽ là trường hợp khi number biến đượ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. Không làm ready dễ bay hơi chính xác vấn đề này?

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

public class NoVisibility {
    riêng boolean tĩnh đã sẵn sàng;
    số int tĩnh riêng;
    lớp đọc tĩnh riêng tư ReadThread mở rộng Thread {
        public void run () {
            trong khi (đã sẵn sàng) Thread.yield ();
            System.out.println (số);
        }
    }
    public static void main (String [] args) {
        mới ReaderThread (). start ();
        số = 42;
        ready = true;
    }
}

76
2018-02-08 15:25


gốc


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 việc chúng là các biến tĩnh, các trường đối tượng hay các phần tử mảng, tất cả chúng đều có cùng các cân nhắc. (Với vấn đề mà các phần tử mảng không thể được tạo ra dễ bay hơi.) - Paŭlo Ebermann
hỏi giáo viên của bạn loại kiến ​​trúc mà anh cho là có thể thấy '0'. Tuy nhiên, về lý thuyết anh ấy đúng. - bestsss
@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. - David Schwartz
Chúng được chia sẻ giữa mọi thứ được nạp bởi cùng một trình nạp lớp. Bao gồm chủ đề. - user207421
Giáo viên của bạn (và câu trả lời được chấp nhận) là 100% đúng, nhưng tôi sẽ đề cập rằng nó hiếm khi xảy ra - đây là loại vấn đề sẽ ẩn trong nhiều năm và chỉ hiển thị khi nó có hại nhất. Ngay cả các thử nghiệm ngắn cố gắng phơi bày vấn đề có xu hướng hoạt động như thể mọi thứ đều ổn (Có lẽ vì chúng không có thời gian cho JVM làm tối ưu hóa nhiều), vì vậy đó là một vấn đề thực sự tốt. - Bill K


Các câu trả lời:


Không có vấn đề về mức độ 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. Dưới đây là một bài viết nói về mô hình bộ nhớ và cách viết trở nên hiển thị cho các chủ đề. 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ối quan hệ xảy ra trước, đây là một trích dẫn từ liên kết đó (được cung cấp trong bình luận của Jed Wesley-Smith):

Chương 17 của Đặc tả Ngôn ngữ Java định nghĩa mối quan hệ xảy ra trước khi hoạt động bộ nhớ như đọc và ghi các 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 biệt:

  • Mỗi hành động trong một chủ đề xảy ra trước mỗi hành động trong chuỗi đó đến sau trong 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 một trường biến động 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.

  • Một 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 một chủ đề xảy ra trước khi bất kỳ chuỗi nào khác trả về thành công từ một phép nối trên chuỗi đó.


58
2018-02-08 15:31



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
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
Phần cuối cùng trở thành có thể nhìn thấy là sai. Nếu không có bất kỳ mối quan hệ nào xảy ra trước đó, không có sự đảm bảo rằng bất kỳ việc viết nào cũng sẽ không bao giờ được nhìn thấy bởi một chủ đề khác, như JIT là khá trong quyền của mình để foist đọc vào một đăng ký và sau đó bạn sẽ không bao giờ nhìn thấy bất kỳ bản cập nhật. Bất kỳ tải cuối cùng là may mắn và không nên dựa vào. - Jed Wesley-Smith
@bestsss Thread.exit không đảm bảo bất cứ điều gì, nó có hệ quả của việc thực hiện một cạnh xảy ra trước khi thực hiện hiện tại của nó trên máy ảo Sun Java6. Đó không phải là điều bạn có thể dựa vào. - Jed Wesley-Smith
@bestsss cũng phát hiện. Thật không may ThreadGroup bị hỏng theo nhiều cách. - Jed Wesley-Smith


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

Các 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 luồng có thể không hiển thị với một 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 thông tin:

Trước tiên, bạn phải hiểu một chút 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ĩ đến để mô tả nó là nếu bạn tưởng tượng nó theo cách này:

  • Mỗi chuỗi 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 hãy chịu đựng với tôi về vấn đề này).

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

  • Bộ nhớ ghi xảy ra trong một luồng có thể "rò rỉ thông qua" và được xem bởi một luồng khác, nhưng điều này không có nghĩa là được bảo đảm. 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, đây chỉ đơn giản là một mô hình tinh thần để suy nghĩ về luồng và dễ bay hơi, chứ không phải là cách JVM hoạt động.


28
2018-02-08 15:35





Về cơ bản nó là sự thật, nhưng thực sự 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.

Do đó Java định nghĩa Mô hình bộ nhớ, 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, thêm volatile đảm bảo khả năng hiển thị.


9
2018-02-08 15:33





Chúng được "chia sẻ" tất nhiên theo nghĩa là cả hai đều đề cập đến cùng một biến, nhưng chúng không nhất thiết phải nhìn thấy các 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, các bài viết được tạo bởi một luồng khác có thể xuất hiện theo thứ tự khác, trừ khi các biến được khai báo volatile hoặc ghi được đồng bộ hóa rõ ràng.


5
2018-02-08 15:34





Trong một trình nạp lớp đơn, các trường 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.


4
2018-02-08 15:27





Khi bạn khởi tạo biến kiểu nguyên thủy biến java mặc định gán một giá trị cho các biến tĩnh

public static int i ;

khi bạn xác định biến như thế này, giá trị mặc định của i = 0; thats lý do tại sao có một khả năng để giúp bạn có được 0. sau đó chủ đề chính cập nhật giá trị của boolean đã sẵn sàng thành true. kể từ khi sẵn sàng là một biến tĩnh, chủ đề chính và các tham chiếu thread khác đến cùng một địa chỉ bộ nhớ để biến sẵn sàng thay đổi. do đó, sợi thứ cấp thoát ra từ vòng lặp while và giá trị in. khi in giá trị khởi tạo giá trị của số là 0. nếu quá trình luồng đã trôi qua trong khi vòng lặp trước biến số cập nhật chủ đề chính. thì có khả năng in 0


1
2018-04-19 14:31





@dontocsata bạn có thể quay lại với giáo viên của bạn và học cho anh ấy một chút :)

vài ghi chú từ thế giới thực và bất kể những gì bạn thấy hoặc được kể. 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ị.

2 biến sau đây sẽ nằm trên cùng một dòng bộ đệm dưới bất kỳ kiến ​​trúc nào.

private static boolean ready;  
private static int number;  

Thread.exit (chủ đề chính) được đảm bảo thoát và exit được đảm bảo gây ra một hàng rào bộ nhớ, do loại bỏ thread thread thread (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 còn sống vì nó không phải là một daemon! Như vậy ready và number sẽ được xả cùng nhau (hoặc số trước nếu chuyển đổi ngữ cảnh xảy ra) và không có lý do thực sự nào để 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 được. 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).


-2
2018-02-11 00:06



@bestsss trong khi đó là tất cả sự thật ngày hôm nay, nó dựa trên thực hiện 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. - Jed Wesley-Smith
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
Có thể là lời khuyên tồi tệ nhất khi viết mã an toàn thread mà tôi đã thấy. - Lawrence Dol
@Bestsss: Câu trả lời đơn giản cho câu hỏi của bạn chỉ đơn giản là: "Mã cho đặc điểm kỹ thuật và tài liệu, chứ không phải các tác dụng phụ của hệ thống hoặc triển khai cụ thể của bạn". Điều này đặc biệt quan trọng trong một nền tảng máy ảo được thiết kế để trở thành bất khả tri của phần cứng cơ bản. - Lawrence Dol
@Bestsss: Điểm của giáo viên là (a) mã có thể hoạt động tốt khi bạn kiểm tra nó, và (b) mã bị hỏng vì nó phụ thuộc vào hoạt động phần cứng và không phải là sự đảm bảo của thông số. Vấn đề là có vẻ OK, nhưng không ổn. - Lawrence Dol