"안녕하세요, 아미고! 여러 스레드가 공유 리소스에 동시에 액세스하려고 할 때 발생하는 문제에 대해 Ellie가 말한 것을 기억하십니까?"

"예."

"문제는 그것이 전부가 아니라는 것입니다. 또 다른 작은 문제가 있습니다."

아시다시피 컴퓨터에는 데이터와 명령(코드)이 저장되는 메모리와 이러한 명령을 실행하고 데이터로 작업하는 프로세서가 있습니다. 프로세서는 메모리에서 데이터를 읽고 변경한 다음 다시 메모리에 씁니다. 계산 속도를 높이기 위해 프로세서에는 "빠른" 자체 내장 메모리인 캐시가 있습니다.

프로세서는 가장 자주 사용되는 변수와 메모리 영역을 캐시에 복사하여 더 빠르게 실행됩니다. 그런 다음 이 빠른 메모리에 모든 변경 사항을 적용합니다. 그런 다음 데이터를 «느린» 메모리로 다시 복사합니다. 그동안 느린 메모리에는 이전(변경되지 않은!) 변수가 포함되어 있습니다.

여기서 문제가 발생합니다. 한 스레드는 위의 예에서 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();
}

"와우! 그리고 동기화 와 같은 아름다운 솔루션을 생각해 냈습니까  ?"

"당신은 그것을 믿지 않을 것입니다!"

첫 번째 생각은 캐시를 비활성화하는 것이었지만 이로 인해 프로그램 실행 속도가 몇 배 느려졌습니다. 그런 다음 다른 솔루션이 나타났습니다.

휘발성 키워드 탄생했습니다. 우리는 이 키워드를 변수 선언 앞에 넣어 해당 값을 캐시에 넣지 않아야 함을 나타냅니다. 더 정확하게는 캐시에 넣을 수 없다는 것이 아니라 항상 일반(느린) 메모리에서 읽고 써야 한다는 것입니다.

모든 것이 잘 작동하도록 솔루션을 수정하는 방법은 다음과 같습니다.

암호 설명
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");
}
}
}
휘발성 수정자는 변수가 항상 모든 스레드가 공유하는 일반 메모리에서 읽고 쓰도록 합니다.
public static void main(String[] args)
{
Clock clock = new Clock();
Thread clockThread = new Thread(clock);
clockThread.start();

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

"그게 다야?"

"그게 다야. 단순하고 아름다워."