2009-06-13 28 views
11

câu hỏi Có thể tương tự:Tại sao các biến trong Java dễ bay hơi theo mặc định?

Do you ever use the volatile keyword in Java?


Hôm nay tôi đã gỡ lỗi trò chơi của tôi; Nó có một vấn đề rất khó khăn mà sẽ xuất hiện sau mỗi vài phút, nhưng rất khó để tái sản xuất. Vì vậy, đầu tiên tôi thêm từ khóa synchronized vào mỗi phương pháp của tôi. Điều đó không hiệu quả. Sau đó, tôi đã thêm từ khóa volatile vào mọi trường. Vấn đề dường như chỉ sửa chữa chính nó.

Sau một số thử nghiệm, tôi thấy rằng trường chịu trách nhiệm là đối tượng GameState theo dõi trạng thái hiện tại của trò chơi của tôi, có thể phát hoặc bận. Khi bận, trò chơi sẽ bỏ qua đầu vào của người dùng. Những gì tôi có là một chuỗi liên tục thay đổi biến số state, trong khi chuỗi sự kiện đọc biến số state. Tuy nhiên, sau khi một chủ đề thay đổi biến, phải mất vài giây để chuỗi khác nhận ra các thay đổi, điều này cuối cùng gây ra sự cố.

Nó đã được sửa bằng cách tạo biến trạng thái volatile.

Tại sao các biến trong Java volatile theo mặc định và lý do không sử dụng từ khóa volatile là gì?

+11

Vì cùng một phương pháp lý do không được đồng bộ hóa theo mặc định. –

+6

@mP: Tôi không nghĩ vậy. Dễ bay hơi và đồng bộ là không tương đương: biến động không thể gây ra deadlocks. –

Trả lời

32

Để tạo một câu chuyện dài, các biến dễ bay hơi - chúng trong Java hoặc C# - không bao giờ được lưu trong bộ nhớ cache cục bộ trong chuỗi. Điều này không có nhiều ngụ ý trừ khi bạn đang xử lý CPU đa bộ xử lý/đa lõi với các luồng thực thi trên các lõi khác nhau, vì chúng sẽ xem xét cùng một bộ nhớ cache. Khi bạn khai báo một biến là dễ bay hơi, tất cả các lần đọc và ghi đến thẳng và đi thẳng đến vị trí bộ nhớ chính thực tế; không có bộ nhớ cache nào liên quan. Điều này có ý nghĩa khi nói đến tối ưu hóa, và làm như vậy một cách không cần thiết (khi hầu hết các biến không cần phải dễ bay hơi) sẽ gây ra một hình phạt hiệu suất (ít khi nó có thể hoặc có thể không) cho một mức tăng tương đối nhỏ.

+6

Ngay cả với một bộ xử lý lõi đơn, trình biên dịch (bao gồm trình biên dịch JIT) có thể quyết định tối ưu hóa truy cập vào biến. –

+0

@ Chris: Tôi đến từ một nền .NET, vì vậy Java có thể khác, nhưng tôi không nghĩ rằng một biến mẫu có thể được tối ưu hóa trừ khi nó phát hiện ra nó chưa bao giờ được sử dụng, và các biến cục bộ không thể khai báo dễ bay hơi (sẽ không thực sự cần), phải không? –

+8

@Adam: Nó hoàn toàn không thể tối ưu hóa nó. Tuy nhiên, một cái gì đó như "y = x * x" chỉ có thể đi vào bộ nhớ để có được giá trị của x một lần, thay vì hai lần. Đó là một ví dụ tầm thường ... bây giờ tưởng tượng rằng x được trải ra trong một số vòng lặp và kiểm tra nhiều lần. Nếu trình biên dịch không bao giờ thấy bạn thay đổi giá trị của x, nó có thể quyết định đọc nó một lần và giữ nó xung quanh cục bộ. –

5

Bởi vì trình biên dịch không thể tối ưu hóa biến dễ bay hơi.

volatile cho trình biên dịch biết rằng biến có thể thay đổi bất kỳ lúc nào. Vì vậy, nó không thể giả định rằng biến sẽ không thay đổi và tối ưu hóa cho phù hợp.

2

Tuyên bố biến dễ bay hơi thường có tác động rất lớn đến hiệu suất. Trên các hệ thống đơn luồng truyền thống, nó tương đối dễ dàng để biết những gì cần thiết để dễ bay hơi; đó là những thứ truy cập phần cứng.

Trên đa luồng có thể phức tạp hơn một chút, nhưng tôi thường khuyến khích sử dụng thông báo và hàng đợi sự kiện để xử lý dữ liệu truyền giữa các lệnh trong các biến ma thuật. Trong Java nó có thể không quan trọng nhiều; trong C/C++ bạn sẽ gặp rắc rối khi các biến đó không thể được thiết lập bằng phần cứng cơ bản.

+0

Tôi tò mò, kinh nghiệm duy nhất của tôi với các biến biến động là đặc biệt cho truy cập đa luồng. Điều gì sẽ là lý do đằng sau biến biến động trong một môi trường đơn luồng? –

+1

@Adam: Trong Java và C#, tôi không chắc lắm, mặc dù tôi nghĩ rằng JNI có thể cho phép bạn chơi một số trò chơi. Trong môi trường C/C++, dễ bay hơi là quan trọng để chỉ ra rằng bộ nhớ này có thể được thay đổi từ một nơi khác ngoài chương trình đang chạy, tức là phần cứng được ánh xạ tới vị trí bộ nhớ đó. Vì Java và C# thường không chạy gần với phần cứng, nó có thể là một trò chơi khác. –

+0

@AdamRobinson: Ngay cả trong môi trường luồng đơn, Java có quyền sắp xếp lại các lệnh độc lập. Vì vậy, ví dụ: 'x = 1; y = 2; 'có thể được thay đổi thành' y = 2; x = 1; '. Nhưng 'x = z ++; y = z ++; 'không được thay đổi thành' y = z ++; x = z ++; '. JVM không được phép sắp xếp lại các lệnh có khả năng hiển thị bên ngoài. Ví dụ 'x = 1; System.out.println ("x được đặt"); y = 2; 'không thể được sắp xếp lại. Nếu bạn không tin tưởng Javas bên ngoài khả năng hiển thị logic (nói rằng nếu bạn đang theo dõi bộ nhớ thực tế), bạn có thể sử dụng dễ bay hơi để đánh dấu biến như một cái gì đó mà không nên được tối ưu hóa. –

6

Trong khi những người khác là chính xác trong việc chỉ ra lý do tại sao nó sẽ là một ý tưởng tồi để mặc định để dễ bay hơi, có một điểm khác để thực hiện: có rất có thể là một lỗi trong mã của bạn. Biến hiếm khi cần phải biến động: luôn có cách để đồng bộ hóa quyền truy cập vào các biến (hoặc bằng từ khóa được đồng bộ hóa hoặc sử dụng đối tượng AtomicXxx từ java.util.concurrency): ngoại lệ sẽ bao gồm mã JNI thao tác (không bị ràng buộc bởi chỉ thị đồng bộ hóa).

Vì vậy, thay vì thêm biến động, bạn có thể muốn tìm ra TẠI SAO nó giải quyết được sự cố. Nó không phải là cách duy nhất để giải quyết nó, và có lẽ là một cách tốt hơn.

+1

Mặc dù điều này có thể hoặc không đúng, nhưng hoàn toàn có thể là anh ấy đang cố viết mã không có khóa. Nó không phải luôn luôn cần thiết để có được một khóa độc quyền hoặc sử dụng một semaphore nếu tất cả các bạn đang làm là đọc. Chỉ cần của tôi .02 –

+0

Đúng, có thể, và bay hơi là một cách. Nhưng chi phí của biến động (do những lý do khác chỉ ra) có thể vượt quá mức đồng bộ hóa rõ ràng và không thể thấp hơn. Đặc biệt được đưa ra như thế nào j.u.concurrent được tối ưu hóa là, hoặc thậm chí đồng bộ cũ đồng bằng. – StaxMan

10

Các chất dễ bay hơi chỉ thực sự cần thiết khi bạn đang cố viết mã an toàn, không có khóa ở mức độ thấp. Hầu hết mã của bạn có thể không được là hoặc an toàn chủ đề hoặc không có khóa. Theo kinh nghiệm của tôi, lập trình không có khóa chỉ đáng để thử sau khi bạn thấy rằng phiên bản đơn giản hơn mà thực hiện việc khóa đang phát sinh hiệu suất đáng kể do khóa.

Cách thay thế dễ chịu hơn là sử dụng các khối xây dựng khác trong java.util.concurrent, một số trong số đó không có khóa nhưng không gây rối với đầu của bạn nhiều như cố gắng tự làm ở mức thấp.

Biến động có chi phí hiệu suất của riêng nó và không có lý do gì khiến mã hầu hết phải chịu các chi phí đó.

8

Cá nhân tôi cho rằng các trường phải được đặt ở vị trí cuối cùng theo mặc định và chỉ có thể thay đổi bằng từ khóa bổ sung, nhưng thuyền đó đã đi thuyền buồm cùng lúc trước. ;)

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