"Xin chào, Amigo! Bạn có nhớ rằng Ellie đã nói với bạn về các vấn đề phát sinh khi một số luồng cố gắng truy cập đồng thời vào một tài nguyên được chia sẻ không?"

"Đúng."

"Vấn đề là, đó không phải là tất cả. Còn một vấn đề nhỏ nữa."

Như bạn đã biết, máy tính có bộ nhớ lưu trữ dữ liệu và lệnh (mã), cũng như bộ xử lý thực thi các lệnh này và làm việc với dữ liệu. Bộ xử lý đọc dữ liệu từ bộ nhớ, thay đổi và ghi lại vào bộ nhớ. Để tăng tốc độ tính toán, bộ xử lý có bộ nhớ "nhanh" tích hợp của riêng nó: bộ đệm.

Bộ xử lý chạy nhanh hơn bằng cách sao chép các biến và vùng bộ nhớ được sử dụng thường xuyên nhất vào bộ đệm của nó. Sau đó, nó thực hiện tất cả các thay đổi trong bộ nhớ nhanh này. Và sau đó nó sao chép dữ liệu trở lại bộ nhớ «chậm». Trong khi đó, bộ nhớ chậm chứa các biến cũ (không thay đổi!).

Đây là nơi phát sinh vấn đề. Một luồng thay đổi một biến , chẳng hạn như isCancel hoặc isInterrupted trong ví dụ trên, nhưng luồng thứ hai «không thấy thay đổi này , vì nó xảy ra trong bộ nhớ nhanh. Đây là hậu quả của việc các luồng không có quyền truy cập vào bộ đệm của nhau. (Một bộ xử lý thường chứa một số lõi độc lập và các luồng có thể chạy trên các lõi vật lý khác nhau.)

Hãy nhớ lại ví dụ ngày hôm qua:

Mã số Sự miêu tả
class Clock implements Runnable
{
private boolean isCancel = false;

public void cancel()
{
this.isCancel = true;
}

public void run()
{
while (!this.isCancel)
{
Thread.sleep(1000);
System.out.println("Tick");
}
}
}
Chủ đề «không biết» rằng các chủ đề khác tồn tại.

Trong phương thức run, biến isCancel được đưa vào bộ đệm của luồng con khi nó được sử dụng lần đầu tiên. Thao tác này tương đương với đoạn mã sau:

public void run()
{
boolean isCancelCached = this.isCancel;
while (!isCancelCached)
{
Thread.sleep(1000);
System.out.println("Tick");
}
}

Việc gọi phương thức hủy từ một luồng khác sẽ thay đổi giá trị của isCancel trong bộ nhớ bình thường (chậm), nhưng không thay đổi trong bộ đệm của các luồng khác.

public static void main(String[] args)
{
Clock clock = new Clock();
Thread clockThread = new Thread(clock);
clockThread.start();

Thread.sleep(10000);
clock.cancel();
}

"Chà! Và họ cũng đã nghĩ ra một giải pháp tuyệt vời cho việc này, chẳng hạn như  đồng bộ hóa ?"

"Ngươi sẽ không tin!"

Ý tưởng đầu tiên là vô hiệu hóa bộ đệm, nhưng điều này làm cho các chương trình chạy chậm hơn nhiều lần. Sau đó, một giải pháp khác xuất hiện.

Từ khóa dễ bay hơi đã ra đời. Chúng tôi đặt từ khóa này trước một khai báo biến để chỉ ra rằng giá trị của nó không được đưa vào bộ đệm. Chính xác hơn, không phải là nó không thể được đưa vào bộ đệm, mà đơn giản là nó luôn phải được đọc và ghi vào bộ nhớ (chậm) thông thường.

Đây là cách khắc phục giải pháp của chúng tôi để mọi thứ hoạt động tốt:

Mã số Sự miêu tả
class Clock implements Runnable
{
private volatile boolean isCancel = false;

public void cancel()
{
this.isCancel = true;
}

public void run()
{
while (!this.isCancel)
{
Thread.sleep(1000);
System.out.println("Tick");
}
}
}
Công cụ sửa đổi dễ bay hơi làm cho một biến luôn được đọc và ghi vào bộ nhớ bình thường được chia sẻ bởi tất cả các luồng.
public static void main(String[] args)
{
Clock clock = new Clock();
Thread clockThread = new Thread(clock);
clockThread.start();

Thread.sleep(10000);
clock.cancel();
}

"Đó là nó?"

"Vậy đó. Đơn giản và đẹp."