답변:
C #과 Java 모두 "휘발성"은 변수 자체의 값이 프로그램 자체의 범위 밖에서 변경 될 수 있으므로 변수의 값을 캐시해서는 안된다는 것을 컴파일러에 알려줍니다. 그런 다음 컴파일러는 변수가 "제어 범위를 벗어나"변경되면 문제를 일으킬 수있는 최적화를 피합니다.
이 예제를 고려하십시오.
int i = 5;
System.out.println(i);
컴파일러는 다음과 같이 5를 인쇄하도록 최적화 할 수 있습니다.
System.out.println(5);
그러나 변경할 수있는 다른 스레드 i
가있는 경우 잘못된 동작입니다. 다른 스레드 i
가 6으로 변경 되면 최적화 된 버전은 여전히 5를 인쇄합니다.
volatile
키워드는 최적화 및 캐싱을 방지하며, 따라서 변수가 다른 스레드에 의해 변경 될 수있는 경우 유용하다.
i
표시된 최적화가 유효하다고 생각합니다 volatile
. Java에서는 모든 것이 이전 에 발생한 관계에 관한 것입니다.
i
지역 변수이고, 다른 쓰레드는 어쨌든 그것을 변경할 수 있습니다. 필드 인 경우 컴파일러가이 아닌 경우 호출을 최적화 할 수 없습니다 final
. 필자는 필드가 final
명시 적으로 선언되지 않은 경우 필드가 "보인다"는 가정에 따라 컴파일러가 최적화를 할 수 있다고 생각하지 않습니다 .
휘발성이 변수에 어떤 영향을 미치는지 이해하려면 변수가 휘발성이 아닌 경우 어떻게되는지 이해하는 것이 중요합니다.
두 개의 스레드 A & B가 비 휘발성 변수에 액세스 할 때 각 스레드는 변수의 로컬 사본을 로컬 캐시에 유지합니다. 로컬 캐시에서 스레드 A가 수행 한 변경 내용은 스레드 B에 표시되지 않습니다.
변수가 휘발성으로 선언되면 이는 스레드가 그러한 변수를 캐시해서는 안된다는 의미입니다. 즉, 스레드는 주 메모리에서 직접 읽지 않는 한 이러한 변수의 값을 신뢰해서는 안됩니다.
그렇다면 언제 변수를 휘발성으로 만들어야합니까?
많은 스레드가 액세스 할 수있는 변수가 있고 값이 프로그램의 다른 스레드 / 프로세스 / 외부에서 업데이트 된 경우에도 모든 스레드가 해당 변수의 최신 업데이트 값을 얻으려고 할 때.
휘발성 필드의 읽기는 의미를 얻습니다 . 이는 휘발성 변수에서 읽은 메모리가 다음 메모리를 읽기 전에 발생한다는 것을 보장합니다. 컴파일러가 재정렬을 수행하지 못하도록 차단하고 하드웨어에 필요한 경우 (약한 순서의 CPU), 특수 명령을 사용하여 휘발성 읽기 이후에 발생하지만 예측 적으로 일찍 시작되거나 CPU가 읽을 수있는 모든 읽기를 하드웨어가 플러시하도록합니다. 부하 획득 문제와 폐기 시점 사이에 투기 적 부하가 발생하지 않도록하여 초기에 조기에 발행되는 것을 방지합니다.
휘발성 필드의 쓰기에는 릴리스 시맨틱이 있습니다. 이는 모든 이전 메모리 쓰기가 다른 프로세서에 표시 될 때까지 휘발성 변수에 대한 모든 메모리 쓰기가 지연되도록 보장합니다.
다음 예제를 고려하십시오.
something.foo = new Thing();
경우 foo
클래스의 멤버 변수이고, 다른 CPU에 의해 언급 된 개체 인스턴스에 액세스 할 수 있습니다 something
, 그들은 값이 표시 될 수 있습니다 foo
변화를 하기 전에 의 메모리 쓰기 Thing
생성자는 전 세계적으로 볼 수 있습니다! 이것이 "약하게 정렬 된 메모리"라는 의미입니다. 컴파일러가에 저장하기 전에 생성자에 모든 저장이있는 경우에도 발생할 수 있습니다 foo
. 경우 foo
입니다 volatile
후하는 가게 foo
릴리스 의미를 가질 것이며, 하드웨어 보증은 쓰기 전에 쓰기의 모든 것을에 foo
쓰기가 허용하기 전에 다른 프로세서에 볼 수 있습니다 foo
발생할 수 있습니다.
쓰기 foo
순서가 너무 나빠질 수 있는 방법은 무엇입니까? 캐시 라인 보유 foo
가 캐시에 있고 생성자의 저장소가 캐시를 놓친 경우 캐시 누락에 대한 쓰기보다 훨씬 빨리 저장소를 완료 할 수 있습니다.
인텔의 (끔찍한) Itanium 아키텍처는 메모리를 약하게 주문했습니다. 원래 XBox 360에 사용 된 프로세서의 메모리가 약합니다. 널리 사용되는 ARMv7-A를 포함한 많은 ARM 프로세서는 메모리를 약하게 주문했습니다.
잠금과 같은 것들이 본질적으로 동시에 의미를 획득하고 릴리스하는 것과 동일한 전체 메모리 장벽을 수행하기 때문에 개발자들은 종종 이러한 데이터 경쟁을 보지 못합니다. 잠금을 획득하기 전에 잠금 내부의로드를 추론 적으로 실행할 수 없으며 잠금을 획득 할 때까지 지연됩니다. 잠금 해제에서 저장을 지연시킬 수 없으며 잠금 해제 명령은 잠금 내부에서 수행 된 모든 쓰기가 전체적으로 표시 될 때까지 지연됩니다.
보다 완전한 예는 "이중 체크 잠금"패턴입니다. 이 패턴의 목적은 객체를 지연 초기화하기 위해 항상 잠금을 획득하지 않아도되는 것입니다.
Wikipedia에서 n
public class MySingleton {
private static object myLock = new object();
private static volatile MySingleton mySingleton = null;
private MySingleton() {
}
public static MySingleton GetInstance() {
if (mySingleton == null) { // 1st check
lock (myLock) {
if (mySingleton == null) { // 2nd (double) check
mySingleton = new MySingleton();
// Write-release semantics are implicitly handled by marking
// mySingleton with 'volatile', which inserts the necessary memory
// barriers between the constructor call and the write to mySingleton.
// The barriers created by the lock are not sufficient because
// the object is made visible before the lock is released.
}
}
}
// The barriers created by the lock are not sufficient because not all threads
// will acquire the lock. A fence for read-acquire semantics is needed between
// the test of mySingleton (above) and the use of its contents. This fence
// is automatically inserted because mySingleton is marked as 'volatile'.
return mySingleton;
}
}
이 예에서 MySingleton
생성자에있는 상점은에 저장하기 전에 다른 프로세서에 표시되지 않을 수 있습니다 mySingleton
. 이런 일이 발생하면 mySingleton을 들여다 보는 다른 스레드는 잠금을 얻지 못하며 반드시 생성자에 대한 쓰기를 수행하지는 않습니다.
volatile
캐싱을 방지하지 않습니다. 이 기능은 다른 프로세서가 "참조"하는 순서를 보장합니다. 상점 릴리스는 보류중인 모든 쓰기가 완료되고 관련 회선이 캐시 된 경우 다른 프로세서가 캐시 회선을 버리고 / 기록하도록 지시하는 버스주기가 발행 될 때까지 저장소를 지연시킵니다. 로드 획득은 추측 된 읽기를 플러시하여 과거의 오래된 값이되지 않도록합니다.
head
와 tail
필요가 가정에서 생산을 방지하기 위해 휘발성이 될 tail
하지 않습니다 변화를, 그리고 가정에서 소비자를 방지하기 위해 head
변경되지 않습니다. 또한 head
저장소가 전체적으로 표시되기 전에 큐 데이터 쓰기가 전체적으로 표시되도록 휘발성이어야합니다 head
.
휘발성 키워드는 모두 자바와 C #에서 다른 의미를 가지고있다.
로부터 Java 언어 사양 :
필드는 휘발성으로 선언 될 수 있으며,이 경우 Java 메모리 모델은 모든 스레드가 변수에 대해 일관된 값을 볼 수 있도록합니다.
volatile 키워드 에 대한 C # 참조에서 :
volatile 키워드는 운영 체제, 하드웨어 또는 동시에 실행되는 스레드와 같은 것으로 프로그램에서 필드를 수정할 수 있음을 나타냅니다.
Java에서 "휘발성"은 JVM에 변수가 동시에 여러 스레드에서 사용될 수 있음을 알리는 데 사용되므로 특정 공통 최적화를 적용 할 수 없습니다.
특히 동일한 변수에 액세스하는 두 스레드가 동일한 시스템의 별도 CPU에서 실행되는 상황입니다. 메모리 액세스가 캐시 액세스보다 훨씬 느리기 때문에 CPU가 보유한 데이터를 적극적으로 캐시하는 것이 매우 일반적입니다. 즉 , CPU1에서 데이터가 업데이트 되면 캐시가 자체적으로 지울 때가 아니라 즉시 모든 캐시와 주 메모리로 이동해야 CPU2가 업데이트 된 값을 볼 수 있습니다 (다시 모든 캐시를 무시 함).
비 휘발성 데이터를 읽을 때 실행 스레드가 항상 업데이트 된 값을 얻거나 얻지 못할 수 있습니다. 그러나 개체가 일시적인 경우 스레드는 항상 최신 값을 갖습니다.