Từ khóa dễ bay hơi hữu ích cho


671

Trong công việc hôm nay, tôi đã xem qua volatiletừ khóa trong Java. Không quen thuộc với nó, tôi tìm thấy lời giải thích này:

Lý thuyết và thực hành Java: Quản lý sự biến động

Đưa ra chi tiết trong đó bài viết đó giải thích từ khóa đang đề cập, bạn đã bao giờ sử dụng nó hay bạn có thể thấy một trường hợp mà bạn có thể sử dụng từ khóa này một cách chính xác không?

Câu trả lời:


741

volatilecó ngữ nghĩa cho khả năng hiển thị bộ nhớ. Về cơ bản, giá trị của một volatiletrường sẽ hiển thị cho tất cả các độc giả (đặc biệt là các luồng khác) sau khi thao tác ghi hoàn thành trên nó. Không có volatile, độc giả có thể thấy một số giá trị không được cập nhật.

Để trả lời câu hỏi của bạn: Có, tôi sử dụng một volatilebiến để kiểm soát xem một số mã có tiếp tục vòng lặp hay không. Vòng lặp kiểm tra volatilegiá trị và tiếp tục nếu có true. Điều kiện có thể được đặt thành falsebằng cách gọi phương thức "dừng". Vòng lặp nhìn thấy falsevà kết thúc khi nó kiểm tra giá trị sau khi phương thức dừng hoàn thành thực thi.

Cuốn sách " Java đồng thời trong thực tiễn ", mà tôi rất khuyến khích, đưa ra một lời giải thích tốt volatile. Cuốn sách này được viết bởi cùng một người đã viết bài báo của IBM được tham chiếu trong câu hỏi (trên thực tế, ông đã trích dẫn cuốn sách của mình ở cuối bài viết đó). Sử dụng của tôi volatilelà những gì bài viết của ông gọi là "cờ trạng thái mẫu 1".

Nếu bạn muốn tìm hiểu thêm về cách volatilehoạt động của trình duyệt, hãy đọc mô hình bộ nhớ Java . Nếu bạn muốn vượt qua mức đó, hãy xem một cuốn sách kiến ​​trúc máy tính tốt như Hennessy & Patterson và đọc về sự kết hợp bộ đệm và tính nhất quán của bộ đệm.


118
Câu trả lời này là chính xác, nhưng không đầy đủ. Nó bỏ qua một thuộc tính quan trọng volatileđi kèm với Mô hình bộ nhớ Java mới được định nghĩa trong JSR 133: rằng khi một luồng đọc một volatilebiến, nó không chỉ thấy giá trị được ghi bởi nó bởi một luồng khác, mà còn tất cả các ghi khác cho các biến khác có thể nhìn thấy trong chủ đề khác tại thời điểm volatileviết. Xem câu trả lời nàytài liệu tham khảo này .
Adam Zalcman

46
Đối với người mới bắt đầu, tôi yêu cầu bạn chứng minh bằng một số mã (vui lòng?)
Hungry Blue Dev

6
Bài viết được liên kết trong câu hỏi có ví dụ mã.
Greg Mattes

Tôi nghĩ rằng liên kết 'Hennessy & Patterson' đã bị hỏng. Và liên kết đến model mô hình bộ nhớ Java 'thực sự dẫn đến Đặc tả ngôn ngữ Java của Oracle' Chương 17. Chủ đề và Khóa '.
Kris

2
@fefrei: ngay lập tức là một thuật ngữ thông tục. Tất nhiên, điều đó không thể được đảm bảo khi cả hai thuật toán lập lịch thực hiện và thời gian thực hiện đều không được chỉ định. Cách duy nhất để một chương trình tìm hiểu xem một lần đọc dễ bay hơi có tiếp theo một lần ghi biến động cụ thể hay không, bằng cách kiểm tra xem giá trị nhìn thấy có phải là giá trị được mong đợi hay không.
Holger

177

Càng, công cụ sửa đổi dễ bay hơi đảm bảo rằng bất kỳ luồng nào đọc một trường sẽ thấy giá trị được viết gần đây nhất. - Josh Bloch

Nếu bạn đang suy nghĩ về việc sử dụng volatile, hãy đọc phần gói java.util.concurrentliên quan đến hành vi nguyên tử.

Bài đăng Wikipedia trên Mẫu Singleton cho thấy không ổn định khi sử dụng.


18
Tại sao có cả hai volatilesynchronizedtừ khóa?
ptkato

5
Bài viết Wikipedia về Mẫu Singleton đã thay đổi rất nhiều kể từ đó và không còn tính năng nói volatileví dụ nữa. Nó có thể được tìm thấy trong một phiên bản lưu trữ .
bskp

1
@ptkato Hai từ khóa này phục vụ các mục đích hoàn toàn khác nhau, vì vậy câu hỏi không có ý nghĩa gì khi so sánh, mặc dù cả hai đều liên quan đến tương tranh. Nó giống như nói "Tại sao có cả hai voidpublictừ khóa".
DavidS

134

Điểm quan trọng về volatile:

  1. Có thể đồng bộ hóa trong Java bằng cách sử dụng các từ khóa synchronizedvà khóa Java volatile.
  2. Trong Java, chúng ta không thể có synchronizedbiến. Sử dụng synchronizedtừ khóa với một biến là bất hợp pháp và sẽ dẫn đến lỗi biên dịch. Thay vì sử dụng synchronizedbiến trong Java, bạn có thể sử dụng volatilebiến java , sẽ hướng dẫn các luồng JVM đọc giá trị củavolatile biến từ bộ nhớ chính và không lưu trữ cục bộ vào bộ đệm.
  3. Nếu một biến không được chia sẻ giữa nhiều luồng thì không cần sử dụng volatiletừ khóa.

nguồn

Ví dụ sử dụng của volatile:

public class Singleton {
    private static volatile Singleton _instance; // volatile variable
    public static Singleton getInstance() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null)
                    _instance = new Singleton();
            }
        }
        return _instance;
    }
}

Chúng tôi đang tạo ra một cách lười biếng tại thời điểm yêu cầu đầu tiên xuất hiện.

Nếu chúng ta không tạo _instancebiến volatilethì Thread đang tạo thể hiện của Singletonkhông thể giao tiếp với luồng khác. Vì vậy, nếu Thread A đang tạo cá thể Singleton và ngay sau khi tạo, CPU bị hỏng, v.v., tất cả các luồng khác sẽ không thể thấy giá trị của_instance là không null và họ sẽ tin rằng nó vẫn được gán null.

Lý do tại sao điều này xảy ra? Bởi vì các luồng của trình đọc không thực hiện bất kỳ khóa nào và cho đến khi luồng của trình ghi ra khỏi một khối được đồng bộ hóa, bộ nhớ sẽ không được đồng bộ hóa và giá trị của _instancesẽ không được cập nhật trong bộ nhớ chính. Với từ khóa Biến động trong Java, điều này được xử lý bởi chính Java và các cập nhật như vậy sẽ được hiển thị bởi tất cả các luồng người đọc.

Kết luận : volatiletừ khóa cũng được sử dụng để truyền đạt nội dung của bộ nhớ giữa các luồng.

Ví dụ sử dụng mà không dễ bay hơi:

public class Singleton{    
    private static Singleton _instance;   //without volatile variable
    public static Singleton getInstance(){   
          if(_instance == null){  
              synchronized(Singleton.class){  
               if(_instance == null) _instance = new Singleton(); 
      } 
     }   
    return _instance;  
    }

Mã ở trên không phải là chủ đề an toàn. Mặc dù nó kiểm tra giá trị của thể hiện một lần nữa trong khối được đồng bộ hóa (vì lý do hiệu năng), trình biên dịch JIT có thể sắp xếp lại mã byte theo cách mà tham chiếu đến thể hiện được đặt trước khi hàm tạo kết thúc thực thi. Điều này có nghĩa là phương thức getInstance () trả về một đối tượng có thể chưa được khởi tạo hoàn toàn. Để làm cho luồng mã an toàn, từ khóa có thể được sử dụng kể từ Java 5 cho biến thể hiện. Các biến được đánh dấu là dễ bay hơi chỉ hiển thị cho các luồng khác khi hàm tạo của đối tượng đã hoàn thành việc thực hiện hoàn toàn.
Nguồn

nhập mô tả hình ảnh ở đây

volatilesử dụng trong Java :

Các trình vòng lặp không nhanh thường được triển khai bằng cách sử dụng bộ volatileđếm trên đối tượng danh sách.

  • Khi danh sách được cập nhật, bộ đếm được tăng lên.
  • Khi một Iteratorđược tạo, giá trị hiện tại của bộ đếm được nhúng trong Iteratorđối tượng.
  • Khi một Iteratorthao tác được thực hiện, phương thức so sánh hai giá trị bộ đếm và ném a ConcurrentModificationExceptionnếu chúng khác nhau.

Việc thực hiện các trình vòng lặp không an toàn thường có trọng lượng nhẹ. Họ thường dựa vào các thuộc tính của cấu trúc dữ liệu của việc thực hiện danh sách cụ thể. Không có mô hình chung.


2
"Các vòng lặp không nhanh thường được thực hiện bằng cách sử dụng truy cập dễ bay hơi" - không còn là trường hợp, quá tốn kém: bugs.java.com/bugdatabase/view_bug.do?bug_id=6625725
Vsevolod Golovanov

kiểm tra kép cho _instance có an toàn không? tôi nghĩ rằng chúng không an toàn ngay cả với sự biến động
Dexters

"sẽ hướng dẫn các luồng JVM đọc giá trị của biến dễ bay hơi từ bộ nhớ chính và không lưu trữ cục bộ." điểm tốt
Humoyun Ahmad

Đối với an toàn luồng, người ta cũng có thể đi với private static final Singleton _instance;.
Chris311

52

volatile là rất hữu ích để dừng chủ đề.

Không phải là bạn nên viết các luồng của riêng mình, Java 1.6 có rất nhiều nhóm luồng đẹp. Nhưng nếu bạn chắc chắn rằng bạn cần một chủ đề, bạn sẽ cần biết làm thế nào để ngăn chặn nó.

Mẫu tôi sử dụng cho chủ đề là:

public class Foo extends Thread {

  private volatile boolean close = false;

  public void run() {
    while(!close) {
      // do work
    }
  }
  public void close() {
    close = true;
    // interrupt here if needed
  }
}

Trong đoạn mã trên, việc đọc luồng closetrong vòng lặp while khác với luồng gọiclose() . Nếu không có biến động, luồng chạy vòng lặp có thể không bao giờ thấy sự thay đổi đóng lại.

Lưu ý rằng không cần đồng bộ hóa


2
Tôi tự hỏi tại sao điều đó thậm chí là cần thiết. Không phải điều đó chỉ cần thiết nếu các luồng khác phải phản ứng về sự thay đổi trạng thái của luồng này theo cách mà việc đồng bộ hóa luồng có nguy hiểm?
Jori

27
@Jori, bạn cần biến động vì luồng đọc gần trong vòng lặp while khác với luồng gọi close (). Nếu không có biến động, luồng chạy vòng lặp có thể không bao giờ thấy sự thay đổi đóng lại.
Pyrolistic

bạn có nói rằng có một lợi thế giữa việc dừng một luồng như thế hoặc sử dụng các phương thức Thread # interrupt () và Thread # isInterrupted () không?
Ricardo Belchior

2
@Pyrolistic - Bạn đã quan sát chủ đề không bao giờ thấy sự thay đổi trong thực tế? Hoặc bạn có thể mở rộng ví dụ để kích hoạt vấn đề đó một cách đáng tin cậy? Tôi tò mò vì tôi biết tôi đã sử dụng (và thấy người khác sử dụng) mã về cơ bản giống với ví dụ nhưng không có volatiletừ khóa và dường như nó luôn hoạt động tốt.
aroth

2
@aroth: với các JVM ngày nay, bạn có thể quan sát thấy rằng trong thực tế, ngay cả với các ví dụ đơn giản nhất, tuy nhiên, bạn không thể tái tạo một cách đáng tin cậy hành vi này. Với các ứng dụng phức tạp hơn, đôi khi bạn có các hành động khác với đảm bảo khả năng hiển thị bộ nhớ trong mã khiến nó hoạt động, điều này đặc biệt nguy hiểm vì bạn không biết tại sao nó hoạt động và một thay đổi đơn giản, rõ ràng không liên quan trong mã của bạn có thể phá vỡ ứng dụng
Holger

31

Một ví dụ phổ biến cho việc sử dụng volatilelà sử dụng một volatile booleanbiến làm cờ để chấm dứt một luồng. Nếu bạn đã bắt đầu một chủ đề và bạn muốn có thể ngắt nó một cách an toàn từ một chủ đề khác, bạn có thể kiểm tra định kỳ một chủ đề. Để ngăn chặn nó, đặt cờ thành đúng. Bằng cách tạo cờ volatile, bạn có thể đảm bảo rằng luồng đang kiểm tra nó sẽ thấy nó đã được đặt vào lần tiếp theo nó kiểm tra mà không cần phải sử dụng một synchronizedkhối.


27

Một biến được khai báo bằng volatiletừ khóa, có hai phẩm chất chính làm cho nó trở nên đặc biệt.

  1. Nếu chúng ta có một biến dễ bay hơi, nó không thể được lưu vào bộ nhớ cache (bộ vi xử lý) của máy tính bởi bất kỳ luồng nào. Truy cập luôn xảy ra từ bộ nhớ chính.

  2. Nếu có một hoạt động ghi đang diễn ra một biến động và đột nhiên một hoạt động đọc được yêu cầu, đảm bảo rằng hoạt động ghi sẽ kết thúc trước hoạt động đọc .

Hai phẩm chất trên suy ra rằng

  • Tất cả các chủ đề đọc một biến dễ bay hơi chắc chắn sẽ đọc giá trị mới nhất. Bởi vì không có giá trị lưu trữ có thể gây ô nhiễm nó. Và yêu cầu đọc cũng sẽ chỉ được cấp sau khi hoàn thành thao tác ghi hiện tại.

Và theo mặt khác,

  • Nếu chúng ta điều tra thêm về số 2 mà tôi đã đề cập, chúng ta có thể thấy volatiletừ khóa đó là một cách lý tưởng để duy trì một biến được chia sẻ có số lượng chủ đề người đọc và chỉ có một chủ đề của nhà văn để truy cập nó. Khi chúng tôi thêm volatiletừ khóa, nó đã được thực hiện. Không có bất kỳ chi phí nào khác về an toàn chủ đề.

Ngược lại,

Chúng tôi không thể chỉ sử dụng volatiletừ khóa để đáp ứng một biến được chia sẻ có nhiều hơn một luồng nhà văn truy cập vào nó .


3
Điều này giải thích sự khác biệt giữa dễ bay hơi và đồng bộ.
Ajay

13

Không ai đã đề cập đến việc xử lý hoạt động đọc và ghi cho loại biến dài và kép. Đọc và ghi là các hoạt động nguyên tử cho các biến tham chiếu và cho hầu hết các biến nguyên thủy, ngoại trừ các loại biến dài và kép, phải sử dụng từ khóa dễ bay hơi để làm hoạt động nguyên tử. @link


Để làm cho nó rõ ràng hơn nữa, KHÔNG CẦN thiết lập một biến động boolean, bởi vì việc đọc và viết của một nguyên tử boolean IS ALREADY.
Kai Wang

2
@KaiWang bạn không cần sử dụng dễ bay hơi trên booleans cho mục đích nguyên tử. Nhưng bạn chắc chắn có thể vì lý do tầm nhìn. Có phải đó là những gì bạn muốn nói?
SusanW

12

Có, dễ bay hơi phải được sử dụng bất cứ khi nào bạn muốn một biến có thể thay đổi được truy cập bởi nhiều luồng. Nó không phải là usecase rất phổ biến vì thông thường bạn cần thực hiện nhiều hơn một thao tác nguyên tử (ví dụ: kiểm tra trạng thái biến trước khi sửa đổi nó), trong trường hợp đó bạn sẽ sử dụng một khối được đồng bộ hóa thay thế.


10

Theo tôi, hai kịch bản quan trọng khác ngoài việc dừng chuỗi trong đó từ khóa dễ bay hơi được sử dụng là:

  1. Cơ chế khóa đôi kiểm tra . Được sử dụng thường xuyên trong mẫu thiết kế Singleton. Trong trường hợp này, đối tượng singleton cần được khai báo là không ổn định .
  2. Wakeup giả . Chủ đề đôi khi có thể thức dậy từ cuộc gọi chờ ngay cả khi không có cuộc gọi thông báo nào được phát ra. Hành vi này được gọi là đánh thức giả. Điều này có thể được chống lại bằng cách sử dụng một biến có điều kiện (cờ boolean). Đặt cuộc gọi Wait () trong một vòng lặp while miễn là cờ đúng. Vì vậy, nếu luồng thức dậy từ cuộc gọi chờ vì bất kỳ lý do nào khác ngoài Thông báo / Thông báo Tất cả thì cờ gặp cờ vẫn đúng và do đó các cuộc gọi chờ lại. Trước khi gọi thông báo đặt cờ này thành đúng. Trong trường hợp này, cờ boolean được khai báo là không ổn định .

Toàn bộ phần # 2 có vẻ rất bối rối, đó là các thông báo bị mất, đánh thức giả và các vấn đề về khả năng hiển thị bộ nhớ. Ngoài ra nếu tất cả các công dụng của cờ được đồng bộ hóa thì biến động là không cần thiết. Tôi nghĩ rằng tôi nhận được quan điểm của bạn nhưng đánh thức giả không phải là thuật ngữ chính xác. Vui lòng làm rõ.
Nathan Hughes

5

Bạn sẽ cần sử dụng từ khóa 'dễ bay hơi' hoặc 'được đồng bộ hóa' và bất kỳ công cụ và kỹ thuật kiểm soát đồng thời nào khác mà bạn có thể có nếu bạn đang phát triển một ứng dụng đa luồng. Ví dụ về ứng dụng đó là các ứng dụng máy tính để bàn.

Nếu bạn đang phát triển một ứng dụng sẽ được triển khai đến máy chủ ứng dụng (Tomcat, JBoss AS, Glassfish, v.v.), bạn không phải tự xử lý kiểm soát tương tranh như máy chủ ứng dụng đã xử lý. Trong thực tế, nếu tôi nhớ chính xác, tiêu chuẩn Java EE cấm mọi kiểm soát đồng thời trong các máy chủ và EJB, vì nó là một phần của lớp 'cơ sở hạ tầng' mà bạn phải giải phóng khỏi việc xử lý nó. Bạn chỉ thực hiện kiểm soát tương tranh trong ứng dụng đó nếu bạn đang triển khai các đối tượng đơn lẻ. Điều này thậm chí đã được giải quyết nếu bạn đan các thành phần của mình bằng cách sử dụng frameworkd như Spring.

Vì vậy, trong hầu hết các trường hợp phát triển Java trong đó ứng dụng là ứng dụng web và sử dụng khung IoC như Spring hoặc EJB, bạn sẽ không cần phải sử dụng 'dễ bay hơi'.


5

volatilechỉ đảm bảo rằng tất cả các chủ đề, ngay cả chính họ, đang tăng lên. Ví dụ: một bộ đếm nhìn thấy cùng một mặt của biến cùng một lúc. Nó không được sử dụng thay vì đồng bộ hóa hoặc nguyên tử hoặc các thứ khác, nó hoàn toàn làm cho việc đọc được đồng bộ hóa. Xin đừng so sánh nó với các từ khóa java khác. Như ví dụ cho thấy các hoạt động biến đổi dưới đây cũng là nguyên tử, chúng thất bại hoặc thành công cùng một lúc.

package io.netty.example.telnet;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static volatile  int a = 0;
    public static void main(String args[]) throws InterruptedException{

        List<Thread> list = new  ArrayList<Thread>();
        for(int i = 0 ; i<11 ;i++){
            list.add(new Pojo());
        }

        for (Thread thread : list) {
            thread.start();
        }

        Thread.sleep(20000);
        System.out.println(a);
    }
}
class Pojo extends Thread{
    int a = 10001;
    public void run() {
        while(a-->0){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Main.a++;
            System.out.println("a = "+Main.a);
        }
    }
}

Ngay cả bạn đặt biến động hoặc không kết quả sẽ luôn luôn khác nhau. Nhưng nếu bạn sử dụng AtomicInteger như dưới đây thì kết quả sẽ luôn như vậy. Điều này cũng tương tự với đồng bộ hóa.

    package io.netty.example.telnet;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;

    public class Main {

        public static volatile  AtomicInteger a = new AtomicInteger(0);
        public static void main(String args[]) throws InterruptedException{

            List<Thread> list = new  ArrayList<Thread>();
            for(int i = 0 ; i<11 ;i++){
                list.add(new Pojo());
            }

            for (Thread thread : list) {
                thread.start();
            }

            Thread.sleep(20000);
            System.out.println(a.get());

        }
    }
    class Pojo extends Thread{
        int a = 10001;
        public void run() {
            while(a-->0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.a.incrementAndGet();
                System.out.println("a = "+Main.a);
            }
        }
    }

4

Vâng, tôi sử dụng nó khá nhiều - nó có thể rất hữu ích cho mã đa luồng. Bài viết bạn chỉ là một bài tốt. Mặc dù có hai điều quan trọng cần ghi nhớ:

  1. Bạn chỉ nên sử dụng dễ bay hơi nếu bạn hoàn toàn hiểu những gì nó làm và làm thế nào nó khác với đồng bộ hóa. Trong nhiều tình huống, sự biến động xuất hiện, trên bề mặt, là một sự thay thế hiệu quả hơn đơn giản hơn để đồng bộ hóa, khi thường hiểu rõ hơn về sự biến động sẽ làm rõ rằng đồng bộ hóa là lựa chọn duy nhất sẽ hoạt động.
  2. dễ bay hơi không thực sự hoạt động trong nhiều JVM cũ hơn, mặc dù đã đồng bộ hóa. Tôi nhớ đã thấy một tài liệu tham chiếu các mức hỗ trợ khác nhau trong các JVM khác nhau nhưng tiếc là tôi không thể tìm thấy nó ngay bây giờ. Chắc chắn xem xét nó nếu bạn đang sử dụng Java trước 1.5 hoặc nếu bạn không có quyền kiểm soát các JVM mà chương trình của bạn sẽ chạy.

4

Mỗi luồng truy cập vào một trường biến động sẽ đọc giá trị hiện tại của nó trước khi tiếp tục, thay vì (có khả năng) sử dụng giá trị được lưu trong bộ nhớ cache.

Chỉ có biến thành viên có thể biến động hoặc thoáng qua.


3

Hoàn toàn đồng ý. . chi phí khóa của chủ đề. Từ khóa dễ bay hơi cho phép bạn đảm bảo rằng khi bạn đọc giá trị mà bạn nhận được giá trị hiện tại và không phải là giá trị được lưu trong bộ nhớ cache đã bị lỗi thời bởi một ghi trên một luồng khác.


3

Có hai cách sử dụng từ khóa dễ bay hơi khác nhau.

  1. Ngăn chặn JVM đọc các giá trị từ thanh ghi (giả sử là bộ đệm) và buộc giá trị của nó phải được đọc từ bộ nhớ.
  2. Giảm nguy cơ lỗi bộ nhớ nhất quán.

Ngăn chặn JVM đọc các giá trị trong thanh ghi và buộc giá trị của nó được đọc từ bộ nhớ.

Một lá cờ bận rộn được sử dụng để ngăn chặn một sợi từ tiếp tục trong khi thiết bị đang bận và cờ không được bảo vệ bởi một ổ khóa:

while (busy) {
    /* do something else */
}

Chuỗi thử nghiệm sẽ tiếp tục khi một luồng khác tắt cờ bận :

busy = 0;

Tuy nhiên, vì bận được truy cập thường xuyên trong luồng thử nghiệm, JVM có thể tối ưu hóa thử nghiệm bằng cách đặt giá trị của bận trong một thanh ghi, sau đó kiểm tra nội dung của thanh ghi mà không đọc giá trị bận trong bộ nhớ trước mỗi thử nghiệm. Chuỗi thử nghiệm sẽ không bao giờ thấy thay đổi bận rộn và luồng khác sẽ chỉ thay đổi giá trị của bận trong bộ nhớ, dẫn đến bế tắc. Tuyên bố cờ bận rộn như biến động buộc giá trị của nó phải được đọc trước mỗi bài kiểm tra.

Giảm nguy cơ lỗi nhất quán bộ nhớ.

Sử dụng các biến dễ bay hơi sẽ giảm nguy cơ lỗi nhất quán bộ nhớ , bởi vì bất kỳ ghi vào biến dễ bay hơi nào cũng sẽ thiết lập "xảy ra trước" mối quan hệ với các lần đọc tiếp theo của cùng biến đó. Điều này có nghĩa là những thay đổi đối với một biến dễ bay hơi luôn được hiển thị cho các luồng khác.

Kỹ thuật đọc, viết mà không có lỗi nhất quán bộ nhớ được gọi là hành động nguyên tử .

Một hành động nguyên tử là một hành động có hiệu quả xảy ra cùng một lúc. Một hành động nguyên tử không thể dừng lại ở giữa: nó hoàn toàn xảy ra hoặc hoàn toàn không xảy ra. Không có tác dụng phụ của một hành động nguyên tử được nhìn thấy cho đến khi hành động hoàn tất.

Dưới đây là những hành động bạn có thể chỉ định đó là nguyên tử:

  • Đọc và viết là nguyên tử cho các biến tham chiếu và cho hầu hết các biến nguyên thủy (tất cả các loại trừ dài và gấp đôi).
  • Đọc và viết là nguyên tử cho tất cả các biến được khai báo là không ổn định (bao gồm cả biến dài và biến kép).

Chúc mừng!


2

Dễ bay hơi sau đây.

1> Đọc và ghi các biến dễ bay hơi bởi các luồng khác nhau luôn từ bộ nhớ, không phải từ bộ nhớ cache hoặc thanh ghi cpu của luồng. Vì vậy, mỗi chủ đề luôn luôn giao dịch với giá trị mới nhất. 2> Khi 2 luồng khác nhau làm việc với cùng một biến hoặc các biến tĩnh trong heap, người ta có thể thấy các hành động khác là không theo thứ tự. Xem blog của jeremy manson về điều này. Nhưng dễ bay hơi giúp ở đây.

Theo mã chạy hoàn toàn cho thấy cách một số luồng có thể thực thi theo thứ tự được xác định trước và in kết quả đầu ra mà không sử dụng từ khóa được đồng bộ hóa.

thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3

Để đạt được điều này, chúng tôi có thể sử dụng mã chạy đầy đủ chính thức sau đây.

public class Solution {
    static volatile int counter = 0;
    static int print = 0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread[] ths = new Thread[4];
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new Thread(new MyRunnable(i, ths.length));
            ths[i].start();
        }
    }
    static class MyRunnable implements Runnable {
        final int thID;
        final int total;
        public MyRunnable(int id, int total) {
            thID = id;
            this.total = total;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (thID == counter) {
                    System.out.println("thread " + thID + " prints " + print);
                    print++;
                    if (print == total)
                        print = 0;
                    counter++;
                    if (counter == total)
                        counter = 0;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        // log it
                    }
                }
            }
        }
    }
}

Liên kết github sau đây có readme, đưa ra lời giải thích phù hợp. https://github.com/sankar4git/voliverse_thread_ordering


2

volatilenói cho một lập trình viên rằng giá trị luôn luôn được cập nhật. Vấn đề là giá trị có thể được lưu trên các loại bộ nhớ phần cứng khác nhau. Ví dụ: nó có thể là các thanh ghi CPU, bộ đệm CPU, RAM ... Các thanh ghi СPU và bộ đệm CPU thuộc về CPU và không thể chia sẻ dữ liệu không giống như RAM đang được giải cứu trong môi trường đa luồng

nhập mô tả hình ảnh ở đây

volatiletừ khóa nói rằng một biến sẽ được đọc và ghi trực tiếp từ / vào bộ nhớ RAM . Nó có một số dấu chân tính toán

Java 5mở rộng volatilebằng cách hỗ trợ happens-before[Giới thiệu]

Việc ghi vào trường biến động xảy ra - trước mỗi lần đọc tiếp theo của trường đó.

volatiletừ khóa không chữa một race conditiontình huống khi một số chủ đề có thể viết một số giá trị cùng một lúc. Câu trả lời là synchronizedtừ khóa [Giới thiệu]

Kết quả là nó chỉ an toàn khi một luồng viết và các luồng khác chỉ đọc volatilegiá trị

dễ bay hơi so với đồng bộ


1

Từ trang tài liệu oracle , nhu cầu về biến dễ bay hơi nảy sinh để khắc phục các vấn đề về tính nhất quán của bộ nhớ:

Sử dụng các biến dễ bay hơi sẽ giảm nguy cơ lỗi nhất quán bộ nhớ, bởi vì bất kỳ ghi vào biến dễ bay nào đều thiết lập mối quan hệ xảy ra trước khi đọc các biến tiếp theo của cùng biến đó.

Điều này có nghĩa là những thay đổi đối với một volatilebiến luôn được hiển thị cho các luồng khác. Điều đó cũng có nghĩa là khi một luồng đọc một biến dễ bay hơi, nó sẽ thấy không chỉ là thay đổi mới nhất đối với volatile, mà còn là tác dụng phụ của mã dẫn đến thay đổi.

Như đã giải thích trong Peter Parkercâu trả lời, nếu không có công cụ volatilesửa đổi, mỗi ngăn xếp của luồng có thể có bản sao biến riêng. Bằng cách thực hiện biến như volatile, các vấn đề nhất quán bộ nhớ đã được khắc phục.

Hãy nhìn vào jenkov trang hướng dẫn của để hiểu rõ hơn.

Hãy xem câu hỏi SE liên quan để biết thêm chi tiết về các trường hợp dễ bay hơi và sử dụng để sử dụng dễ bay hơi:

Sự khác biệt giữa dễ bay hơi và đồng bộ hóa trong Java

Một trường hợp sử dụng thực tế:

Bạn có nhiều luồng, cần in thời gian hiện tại theo một định dạng cụ thể chẳng hạn : java.text.SimpleDateFormat("HH-mm-ss"). Yon có thể có một lớp, chuyển đổi thời gian hiện tại thành SimpleDateFormatvà cập nhật biến cho mỗi một giây. Tất cả các luồng khác chỉ có thể sử dụng biến dễ bay hơi này để in thời gian hiện tại trong tệp nhật ký.


1

Biến dễ bay hơi là đồng bộ hóa trọng lượng nhẹ. Khi khả năng hiển thị dữ liệu mới nhất trong số tất cả các luồng là yêu cầu và tính nguyên tử có thể bị tổn hại, trong các tình huống như vậy Biến biến động phải được ưu tiên. Đọc trên các biến dễ bay hơi luôn trả về việc ghi gần đây nhất được thực hiện bởi bất kỳ luồng nào vì chúng không được lưu trong bộ đăng ký cũng như trong bộ nhớ cache nơi các bộ xử lý khác không thể nhìn thấy. Dễ bay hơi là Khóa miễn phí. Tôi sử dụng biến động, khi kịch bản đáp ứng các tiêu chí như đã đề cập ở trên.


-1

Khóa dễ bay hơi khi được sử dụng với một biến, sẽ đảm bảo rằng các luồng đọc biến này sẽ thấy cùng một giá trị. Bây giờ nếu bạn có nhiều luồng đọc và ghi vào một biến, làm cho biến đó biến động sẽ không đủ và dữ liệu sẽ bị hỏng. Chủ đề hình ảnh đã đọc cùng một giá trị nhưng mỗi người đã thực hiện một số trò chuyện (giả sử tăng một bộ đếm), khi ghi lại vào bộ nhớ, tính toàn vẹn dữ liệu bị vi phạm. Đó là lý do tại sao cần phải thực hiện đồng bộ hóa thay đổi (cách khác nhau là có thể)

Nếu các thay đổi được thực hiện bởi 1 luồng và các luồng khác chỉ cần đọc giá trị này, thì biến động sẽ phù hợp.


-1

biến dễ bay hơi về cơ bản được sử dụng để cập nhật tức thời (tuôn ra) trong dòng bộ đệm chính được chia sẻ một khi nó được cập nhật, để thay đổi được phản ánh cho tất cả các luồng công nhân ngay lập tức.


-2

Dưới đây là một mã rất đơn giản để thể hiện yêu cầu của volatilebiến được sử dụng để kiểm soát thực thi luồng từ luồng khác (đây là một kịch bản volatilebắt buộc).

// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
    public static void main(String[] a) throws Exception {
        Task task = new Task();
        new Thread(task).start();

        Thread.sleep(500);
        long stoppedOn = System.nanoTime();

        task.stop(); // -----> do this to stop the thread

        System.out.println("Stopping on: " + stoppedOn);
    }
}

class Task implements Runnable {
    // Try running with and without 'volatile' here
    private volatile boolean state = true;
    private int i = 0;

    public void stop() {
        state = false;
    } 

    @Override
    public void run() {
        while(state) {
            i++;
        }
        System.out.println(i + "> Stopped on: " + System.nanoTime());
    }
}

Khi volatilekhông được sử dụng: bạn sẽ không bao giờ thấy thông báo 'Đã dừng: xxx ' ngay cả sau khi ' Dừng lại: xxx ' và chương trình tiếp tục chạy.

Stopping on: 1895303906650500

Khi volatileđược sử dụng: bạn sẽ thấy 'Đã dừng: xxx ' ngay lập tức.

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

Bản trình diễn: https://repl.it/repls/SilverAgonizingObjectcode


To downvoter: Quan tâm giải thích tại sao downvote? Nếu điều này không đúng, ít nhất tôi sẽ học được những gì sai. Tôi đã thêm nhận xét tương tự này hai lần, nhưng không biết ai đang xóa đi xóa lại
manikanta

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.