Từ khóa Volatile – Concurrency

Từ khóa volatile trong java được sử dụng để đánh dấu một biến sẽ được lưu trữ trong main memory. Nghĩa là mọi lệnh đọc giá trị các biến volatile đều đọc từ main menory chứ không phải đọc từ CPU cache, việc ghi giá trị cho các biến volatile cũng tương tự sẽ ghi xuống main memory.

Tại sao cần sử dụng Volatile

Trong ứng dụng đa luồng(multithread) khi mỗi luồng(thread) làm việc với các biến non-volatile(không có chứa từ khóa volatile) mỗi thread có thể sao chép chúng từ main memory vào CPU cache để thao tác giúp tăng performance. Nếu máy tính của bạn có nhiều CPU thì có thể mỗi thread sẽ chạy trên một CPU khác nhau đồng nghĩa với việc mỗi thread sẽ sao chép các biến vào CPU cache riêng của chúng. volatile in java

Với các biến non-volatile thì chúng ta sẽ không đảm bảo rằng khi bào JVM đọc dữ liệu từ main memory vào cpu cache và ghi dữ liệu từ CPU cache vào main memory. Chính vì thế giá trị của biến non-volatile có thể khác nhau ở từng thread và giá trị của chúng ở main memory cũng ko đảm bảo tính đồng nhất.

Ví dụ chúng ta có 2 thread đều dùng object SharedObject như sau

public class SharedObject {

    public int counter = 0;

}

Giả sử thread 1 sẽ là thread tăng biến counter lên 1 và thread 2 đọc và xử lý. Nếu counter không phải là một volatile biến thì giá trị counter ở thread 1, main memory và thread 2 có khả năng sẽ khác nhau hoàn toànexample volatile

Chúng ta thấy thread 1 đã update Counter lên 7 mà thread 2 vẫn đang giữ giá trị 0. Vì Thread 1 update Counter lên CPU cache mà không ghi trực tiếp lên main memory. 

Cách sử dụng Volatile 

Ở trường hợp trên chúng ta cần Thread 2 luôn đọc được giá trị cuối cùng được Thread 1 update khi đó phải dùng đến volatile. Lưu ý chỉ đối với trường hợp chỉ có thread 1 update giá trị của Counter và thread 2 lấy và sử dụng.

public class SharedObject {

    public volatile int counter = 0;

}

Flow hoạt động của volatile

Nếu Thread A và Thread B thao tác trên cùng một biến volatile, trong đó chỉ có Thread A ghi giá trị vào biến volatile.

1, Nếu Thread A ghi giá trị trên biến volatile thì thread B sẽ đọc lại giá trị mới vừa được ghi xuống bởi thread A.

Ví dụ

class VolatileTest {

    private static volatile int MY_INT = 0;

    public static void main(String[] args) {


        new Thread(() -> {
            int local_value = MY_INT;
            while (local_value < 5) {
                if (local_value != MY_INT) {
                    System.out.println("Thread B, Incrementing MY_INT to:" + MY_INT);
                    local_value = MY_INT;
                }
            }
        }).start();

        new Thread(() -> {
            int local_value = MY_INT;
            while (MY_INT < 5) {
                System.out.println("Thread A, Incrementing MY_INT to:" + (local_value + 1));
                MY_INT = ++local_value;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();


    }
}

Output:

Thread A, Incrementing MY_INT to:1
Thread B, Incrementing MY_INT to:1
Thread A, Incrementing MY_INT to:2
Thread B, Incrementing MY_INT to:2
Thread A, Incrementing MY_INT to:3
Thread B, Incrementing MY_INT to:3
Thread A, Incrementing MY_INT to:4
Thread B, Incrementing MY_INT to:4
Thread A, Incrementing MY_INT to:5
Thread B, Incrementing MY_INT to:5

Nếu MY_INT không phải là biến volatile thì kết quả là

Output

Thread A, Incrementing MY_INT to:1
Thread B, Incrementing MY_INT to:1
Thread A, Incrementing MY_INT to:2
Thread A, Incrementing MY_INT to:3
Thread A, Incrementing MY_INT to:4
Thread A, Incrementing MY_INT to:5

2, Nếu Thead A đọc giá trị biến volatile từ main memory thì các biến non-volatile cũng đọc lại từ main memory

Ví dụ

public class MyClass {
    private int years;
    private int months
    private volatile int days;

    public int totalDays() {
        int total = this.days;
        total += months * 30;
        total += years * 365;
        return total;
    }
}

Method totalDays() đọc giá trị days từ main memory thì giá trị của monthsyears cũng sẽ được đọc lại từ main memory. Do đó bạn phải đảm bảo rằng giá trị của days phải được đọc đầu tiên để có giá trị cuối cùng từ main memory.

Hạn chế của volatile

Trong thực tế thì có thể xảy ra việc nhiều thread ghi vào biến volatile và sẽ dẫn đến đến giá trị sai bởi vì chuyện đọc ghi đồng thời giữa các thread.

Ví dụ biến counter được khởi tạo tại main memory là 0. Sau đó thread 1 lấy và tăng nó lên 1 cùng lúc đó thread 2 cũng lấy và được giá trị 0 và nó cũng tăng lên 1. Trong khi ở thread 2 giá trị của counter phải là 2.

Chưa kể đến các nếu có nhiều hơn 2 thread và chúng cứ đọc và ghi lại biến counter thì giá trị của nó sẽ không thể nào kiểm soát được.

Khi nào sử dụng volatile 

Như đã đề cập ở trên thì chúng ta chỉ sử dụng volatile khi có hai thread trong đó một thread đọc và một thread ghi vào biến volatile còn các trường hợp khác cần sử dụng đến synchronized.

Lưu ý là khi dùng volatile thì performance của bạn sẽ bị giảm đáng kể vì việc thay vì chúng đọc ghi từ cache thì bây giờ chúng phải làm việc với main memory. Hơn nữa chúng còn làm thay đổi các cơ chế tăng cường performance để đáp ứng việc đọc ghi với các biến volatile như mình đã nêu ở trên như là sau khi đọc biến volatile thì các biến non-volatile cũng được đọc lại etc.

5 5 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x