“你好,阿米戈!你還記得艾莉跟你說過當多個線程試圖同時訪問一個共享資源時會出現的問題吧?”

“是的。”

“問題是,這還不是全部。還有一個小問題。”

如您所知,計算機具有存儲數據和命令(代碼)的內存,以及執行這些命令和處理數據的處理器。處理器從內存中讀取數據,對其進行更改,然後將其寫回內存。為了加快計算速度,處理器有自己內置的“快速”內存:高速緩存。

通過將最常用的變量和內存區域複製到緩存中,處理器運行得更快。然後它在這個快速內存中進行所有更改。然後它將數據複製回“慢速”內存。一直以來,慢速內存包含舊的(未更改的!)變量。

這就是問題所在。一個線程更改了一個變量,例如上例中的 isCancel 或 isInterrupted,但第二個線程«看不到此更改,因為它發生在快速內存中。這是線程無法訪問彼此的緩存這一事實的結果。(處理器通常包含幾個獨立的內核,線程可以在物理上不同的內核上運行。)

讓我們回憶一下昨天的例子:

代碼 描述
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");
}
}
}
該線程“不知道”其他線程的存在。

在run方法中,isCancel變量在第一次使用時被放入子線程的緩存中。這個操作相當於下面的代碼:

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

從另一個線程調用cancel方法將改變正常(慢速)內存中的isCancel值,但不會改變其他線程緩存中的值。

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

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

“哇!他們是否也為此想出了一個漂亮的解決方案,比如 synchronized?”

“你不會相信的!”

第一個想法是禁用緩存,但這使程序運行速度慢了好幾倍。然後出現了一個不同的解決方案。

volatile關鍵字誕生了我們將 this 關鍵字放在變量聲明之前,以指示不得將其值放入緩存中。更準確地說,並不是它不能放入緩存,只是它總是必須從普通(慢速)內存中讀取和寫入。

以下是修復我們的解決方案以便一切正常的方法:

代碼 描述
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");
}
}
}
volatile 修飾符導致變量總是從所有線程共享的普通內存中讀取和寫入。
public static void main(String[] args)
{
Clock clock = new Clock();
Thread clockThread = new Thread(clock);
clockThread.start();

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

“就是這樣?”

“就是這樣。簡單而美麗。”