2017-07-27 19 views
9

Tôi đang cố gắng hiểu cách đa luồng hoạt động trong Java. Tôi hiểu sự khác biệt giữa VolatileSynchronization.Chủ đề Java: Tất cả các biến được chia sẻ có dễ bay hơi không?

Volatile là về khả năng hiển thị và không đảm bảo đồng bộ hóa. Khi chúng ta đang làm việc với các môi trường đa luồng, mỗi luồng tạo ra bản sao riêng của nó trên một bộ đệm cục bộ của biến mà chúng đang xử lý. Khi giá trị này đang được cập nhật, bản cập nhật sẽ xảy ra đầu tiên trong bản sao lưu cục bộ, chứ không phải trong biến thực. Do đó, các chủ đề khác là không thuyết phục về các giá trị mà các luồng khác đang thay đổi. Và đây là nơi volatile xuất hiện trong ảnh. Các trường dễ bay hơi được ghi ngay lập tức thông qua bộ nhớ chính và các lần đọc xuất hiện từ bộ nhớ chính.

Snippet từ Thinking In Java -

Đồng bộ hóa cũng gây đỏ bừng chuyển sang bộ nhớ chính, vì vậy nếu một lĩnh vực là hoàn toàn được bảo vệ bằng các phương pháp đồng bộ hoặc các khối, nó không là cần thiết để làm cho nó dễ bay hơi.

Thông thường chỉ an toàn khi sử dụng biến động thay vì đồng bộ hóa nếu lớp chỉ có một trường có thể thay đổi. Một lần nữa, lựa chọn đầu tiên của bạn nên là sử dụng từ khóa được đồng bộ hóa — đó là cách tiếp cận an toàn nhất và cố gắng làm bất kỳ điều gì khác là nguy hiểm.

Nhưng câu hỏi của tôi là, nếu trong một khối được đồng bộ hóa, biến chia sẻ không dễ bay hơi đang được sửa đổi, các chủ đề khác có thấy dữ liệu cập nhật không? (Vì biến được đề cập là không dễ bay hơi, các chủ đề khác nên đọc dữ liệu cũ từ bộ nhớ cache thay vì bộ nhớ chính chính)

Nếu câu trả lời cho câu hỏi trên là NO, thì tôi có thể kết luận rằng mỗi khi sử dụng đồng bộ hóa, tôi nên đảm bảo rằng các biến được chia sẻ phải được đánh dấu volatile?

Và nếu câu trả lời là YES thì điều đó có nghĩa là tôi luôn có thể sử dụng synchronization thay vì đánh dấu các biến được chia sẻ là volatile?

p.s: Trước khi đặt câu hỏi này, tôi đã đọc qua rất nhiều câu trả lời trên StackOverflow và trên các trang web khác, nhưng tôi không thể tìm thấy câu trả lời cho câu hỏi của mình.

+0

@Sotirios: Tôi chắc chắn rằng câu hỏi của tôi không phải là bản sao của điều này: https://stackoverflow.com/questions/3214938/java-volatile-modifier-and-synchronized-blocks Tôi đang tìm kiếm điều gì đó khác hơn sự khác biệt giữa dễ bay hơi và đồng bộ hóa. Dù sao nhờ liên kết, tôi sẽ đọc qua các câu trả lời. –

+0

_difference_ là gì? Bạn hỏi bạn có cần đánh dấu một biến là 'volatile' nếu bạn chỉ truy cập nó thông qua một khối' synchronized'. Các câu trả lời trong câu hỏi đó giải quyết vấn đề đó. –

Trả lời

11

Để đơn giản hóa một chút:

  • volatile chỉ cung cấp visibility: khi bạn đọc một biến volatile bạn nhận được hai đảm bảo: (1) bạn sẽ thấy ghi mới nhất vào biến, ngay cả khi nó đã được thực hiện trong một thread và (2) tất cả các ghi trước đó volatile viết cũng có thể nhìn thấy.
  • synchronized cho bạn khả năng hiển thị và nguyên tử - luồng quan sát các hành động được thực hiện trong khối synchronized từ khối synchronized bằng cùng một màn hình sẽ nhìn thấy tất cả hoặc không có trong số chúng.

Vì vậy, để trả lời câu hỏi của bạn, không, nếu một biến được ghi vào trong một khối synchronized, bạn không cần phải đánh dấu nó volatile, miễn là bạn luôn luôn đọc rằng biến từ một khối synchronized sử dụng màn hình tương tự .


Dưới đây là một vài ví dụ với biến động:

static class TestVolatile { 
    private int i = 0; 
    private volatile int v = 0; 

    void write() { 
    i = 5; 
    v = 7; 
    } 

    void read() { 
    //assuming write was called beforehand 
    print(i); //could be 0 or 5 
    print(v); //must be 7 
    print(i); //must be 5 
    } 

    void increment() { 
    i = i + 1; //if two threads call the method concurrently 
       //i could be incremented by 1 only, not 2: no atomicity 
    } 
} 

Và một vài ví dụ với synchronized:

static class TestSynchronized { 
    private int i = 0; 
    private int j = 0; 

    void write() { 
    synchronized(this) { 
     i = 5; 
     j = 7; 
    } 
    } 

    void read_OK() { 
    synchronized(this) { 
     //assuming write was called beforehand 
     print(i); //must be 5 
     print(j); //must be 7 
     print(i); //must be 5 
    } 
    } 

    void read_NOT_OK() { 
    synchronized(new Object()) { //not the same monitor 
     //assuming write was called beforehand 
     print(i); //can be 0 or 5 
     print(j); //can be 0 or 7 
    }   
    } 

    void increment() { 
    synchronized(this) { 
     i = i + 1; //atomicity guarantees that if two threads call the method 
       //concurrently, i will be incremented twice 
    } 
    } 
} 
+0

Vì vậy, từ câu trả lời của bạn, tôi hiểu rằng khối đọc đồng bộ hóa luôn xảy ra từ bộ nhớ chính. Nhưng người ta phải cẩn thận để đảm bảo rằng một đọc không dễ bay hơi xảy ra trong một khối đồng bộ hóa. Và do đó nó sẽ là khôn ngoan để đánh dấu các biến chia sẻ như vậy dễ bay hơi chỉ trong trường hợp ngày mai ai đó thực hiện một đọc từ một khối không đồng bộ hóa. –

+1

@RahulDevMishra Không thực sự - bạn thường có hai lựa chọn: (1) làm cho việc đồng bộ hóa chi tiết thực hiện nội bộ và không làm rò rỉ các biến - theo cách đó, mã bên ngoài có thể sử dụng lớp của bạn mà không đáng lo ngại hoặc (2) làm - xem ví dụ: https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedList-java.util.List- – assylias

0

Nếu biến được bảo vệ bởi cùng một màn hình khóa mỗi khi nó được truy cập thì không cần phải làm cho nó dễ bay hơi.

Các khối được đồng bộ hóa thực hiện hai việc: truy cập chuỗi đơn lẻ tới các khu vực được bảo vệ bằng hiệu ứng khóa và hiển thị. Hiệu ứng hiển thị có nghĩa là bất kỳ thay đổi nào được thực hiện cho biến đó trong khi được bảo vệ bằng khóa đó sẽ hiển thị cho bất kỳ chuỗi nào khác đi vào vùng sử dụng nó (khóa).

6

JLS định nghĩa mối quan hệ được gọi là "xảy ra-trước" trên các hướng dẫn trong chương trình. Một phiên bản ngắn có thể được nhìn thấy in the documentation of java.util.concurrent.

Thao tác ghi vào biến được hiển thị bằng thao tác đọc của cùng một biến, nếu ghi "xảy ra trước" "đọc. Bây giờ, nếu cả hai chủ đề truy cập biến đó chỉ bên trong khối đồng bộ hóa, thì lệnh thoát khỏi khối đồng bộ đảm bảo rằng những gì xảy ra trong đó "xảy ra trước" bất cứ điều gì xảy ra sau khóa tiếp theo của cùng một màn hình đồng bộ hóa.

Vì vậy, nếu thread ghi vào biến xbên trong một khối đồng bộ, và thread B đọc từ xrằng bên trong một khối đồng bộ trên cùng một màn hình, sau đó x không cần phải là dễ bay hơi - write " xảy ra trước khi "đọc và kết quả của nó sẽ hiển thị với chuỗi B.

Nhưng nếu chuỗi B đọc biến mà không đồng bộ hóa, thì ngay cả khi chuỗi A đã đồng bộ hóa bên trong, thì không đảm bảo rằng ghi" xảy ra trước " và biến không an toàn - trừ khi đó là volatile. Vì vậy, nếu bạn đảm bảo tất cả truy cập - cả đọc và ghi - nằm trong các khối đồng bộ trên cùng một màn hình, thì bạn có thể dựa vào mối quan hệ "xảy ra trước" để làm cho ghi của bạn hiển thị.

+1

Một điều cần lưu ý là mặc dù bạn đã chính xác từ góc độ tài liệu, nhưng thực tế là rào cản bộ nhớ là _not_ trên mỗi màn hình. I E. nếu Thread A rời khỏi khối 'synchronized' và sau đó Thread B đi vào khối _another_' synchronized', thì luồng B sẽ thấy các cập nhật được thực hiện bởi Chủ đề A. Không có sự đảm bảo về trật tự và chúng có thể trùng lặp, nhưng nếu nó xảy ra theo thứ tự đó sau đó B sẽ thấy các bản cập nhật. – Gray

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