나사산 안전 에는 두 가지 측면 이 있다는 것을 이해하는 것이 중요합니다 .
- 실행 제어
- 메모리 가시성
첫 번째는 코드가 실행될 때 (명령이 실행되는 순서 포함)와 동시에 실행될 수 있는지 여부를 제어하는 것과 관련이 있으며, 두 번째는 수행 된 메모리의 효과가 다른 스레드에 표시 될 때와 관련이 있습니다. 각 CPU는 CPU와 주 메모리 사이에 여러 레벨의 캐시를 가지고 있기 때문에 스레드가 주 메모리의 개인 사본을 확보하고 작업 할 수 있기 때문에 다른 CPU 또는 코어에서 실행중인 스레드는 특정 시점에 "메모리"를 다르게 볼 수 있습니다.
를 사용 synchronized
하면 다른 스레드 가 동일한 객체에 대한 모니터 (또는 잠금) 를 얻지 못 하므로 동일한 객체 에 대한 동기화로 보호 된 모든 코드 블록이 동시에 실행되지 않습니다. 또한 동기화 는 "어쩌면 이전의"메모리 장벽을 만들어 일부 스레드가 잠금을 해제하는 시점까지 수행 된 모든 작업이 잠금 을 획득하기 전에 동일한 잠금 을 획득 한 다른 스레드에 나타나는 것처럼 보일 수 있도록 메모리 가시성 제약 조건 을 발생시킵니다. 실제적인 용어로, 현재 하드웨어에서는 일반적으로 모니터를 획득 할 때 CPU 캐시가 플러시되고 해제 될 때 주 메모리에 쓰며 둘 다 (상대적으로) 비쌉니다.
사용 volatile
힘 한편, 휘발성 변수에 대한 모든 액세스는 (판독 또는 기록)을 효과적으로 CPU 캐시로부터 휘발성 변수를 유지하는, 메인 메모리에 발생한다. 변수의 가시성이 정확해야하고 액세스 순서가 중요하지 않은 일부 작업에 유용 할 수 있습니다. 사용 volatile
도 치료를 변경 long
하고 double
원자로 그들에게 접근을 필요로; 일부 오래된 하드웨어에서는 최신 64 비트 하드웨어가 아닌 잠금이 필요할 수 있습니다. Java 5+에 대한 새로운 (JSR-133) 메모리 모델에서 휘발성의 의미는 메모리 가시성 및 명령 순서와 관련하여 동기화되는 수준만큼 거의 강화되었습니다 ( http://www.cs.umd.edu 참조) . /users/pugh/java/memoryModel/jsr-133-faq.html#volatile). 가시성을 위해 휘발성 필드에 대한 각 액세스는 절반의 동기화처럼 작동합니다.
새로운 메모리 모델에서는 여전히 휘발성 변수를 서로 재정렬 할 수 없습니다. 차이점은 이제 더 이상 일반 필드 액세스를 재정렬하기가 쉽지 않다는 것입니다. 휘발성 필드에 쓰는 것은 모니터 릴리즈와 동일한 메모리 효과를 가지며 휘발성 필드에서 읽는 것은 모니터가 획득하는 것과 동일한 메모리 효과를 갖습니다. 실제로, 새로운 메모리 모델은 다른 필드 액세스 (휘발성 또는 비 휘발성)에 의한 휘발성 필드 액세스의 순서를 다시 지정하는 데보다 엄격한 제약을 가하기 때문에 휘발성 필드에 A
쓸 때 스레드에서 볼 수있는 것은 읽을 때 f
스레드에 표시됩니다 .B
f
- JSR 133 (자바 메모리 모델) 자주 묻는 질문
따라서 현재 JMM 아래의 두 가지 형태의 메모리 장벽은 명령어 재정렬 장벽을 유발하여 컴파일러 나 런타임이 장벽을 넘어 명령을 재정렬하지 못하게합니다. 이전 JMM에서는 휘발성이 재정렬을 방해하지 않았습니다. 메모리 장벽을 제외하고 부과되는 유일한 제한은 특정 스레드 에 대해 명령이 명령이 명령 순서에 나타난 순서대로 정확하게 실행 된 경우와 동일하다는 것입니다. 출처.
휘발성의 한 가지 용도는 공유되지만 변경 불가능한 객체를 즉시 재생하는 것입니다. 다른 많은 스레드가 실행주기의 특정 시점에서 객체를 참조합니다. 하나는 게시 된 객체가 다시 생성 된 객체를 사용하기 시작하려면 다른 스레드가 필요하지만 전체 동기화에 대한 추가 오버 헤드가 필요하지 않으며 수행자 경합 및 캐시 플러시가 필요합니다.
// Declaration
public class SharedLocation {
static public SomeObject someObject=new SomeObject(); // default object
}
// Publishing code
// Note: do not simply use SharedLocation.someObject.xxx(), since although
// someObject will be internally consistent for xxx(), a subsequent
// call to yyy() might be inconsistent with xxx() if the object was
// replaced in between calls.
SharedLocation.someObject=new SomeObject(...); // new object is published
// Using code
private String getError() {
SomeObject myCopy=SharedLocation.someObject; // gets current copy
...
int cod=myCopy.getErrorCode();
String txt=myCopy.getErrorText();
return (cod+" - "+txt);
}
// And so on, with myCopy always in a consistent state within and across calls
// Eventually we will return to the code that gets the current SomeObject.
읽기-쓰기-쓰기 질문에 대해 구체적으로 말하십시오. 다음과 같은 안전하지 않은 코드를 고려하십시오.
public void updateCounter() {
if(counter==1000) { counter=0; }
else { counter++; }
}
이제 updateCounter () 메소드가 비동기 화되면 두 개의 스레드가 동시에 입력 할 수 있습니다. 발생할 수있는 많은 순열 중 하나는 thread-1이 counter == 1000에 대한 테스트를 수행하여 true를 찾은 다음 일시 중단한다는 것입니다. 그런 다음 thread-2는 동일한 테스트를 수행하고 또한 사실을 확인하고 일시 중단됩니다. 그런 다음 thread-1이 재개되고 카운터를 0으로 설정합니다. 그러면 thread-2가 재개되고 다시 thread-1에서 업데이트를 놓쳤으므로 카운터를 0으로 설정합니다. 위에서 설명한 것처럼 스레드 전환이 발생하지 않더라도 두 개의 서로 다른 캐시 된 카운터 사본이 두 개의 다른 CPU 코어에 존재하고 스레드가 각각 별도의 코어에서 실행 되었기 때문에 이런 일이 발생할 수 있습니다. 그 문제에 대해 하나의 스레드는 하나의 값으로 카운터를 가질 수 있고 다른 스레드는 캐싱으로 인해 완전히 다른 값으로 카운터를 가질 수 있습니다.
이 예제에서 중요한 것은 변수 카운터 가 메인 메모리에서 캐시로 읽히고, 캐시에서 업데이트되고, 나중에 메모리 장벽이 발생하거나 캐시 메모리가 다른 것에 필요할 때 불확실한 시점에 메인 메모리로 다시 쓰여지는 것입니다. volatile
최대 및 할당 테스트는 원자 적이 지 않은 read+increment+write
기계 명령어 세트 인 증분을 포함하여 이산 연산이므로 카운터를 만드는 것은 이 코드의 스레드 안전에 충분하지 않습니다 .
MOV EAX,counter
INC EAX
MOV counter,EAX
휘발성 변수는 완전히 형성된 객체에 대한 참조 만 읽거나 쓰는 (예를 들어 일반적으로 단일 지점에서만 작성되는) 예제와 같이 모든 작업이 "원자"인 경우에만 유용 합니다. 또 다른 예로는 쓰기시 복사 목록을 지원하는 휘발성 배열 참조가 있습니다. 단, 배열에 대한 참조의 로컬 사본을 먼저 가져 와서 만 배열을 읽은 경우입니다.