내부적으로 어떻게 작동 하는지에 대해 구체적으로 묻는 것이므로 다음과 같습니다.
동기화 안함
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
기본적으로 메모리에서 값을 읽고 증가시키고 메모리로 되돌립니다. 이것은 단일 스레드에서 작동하지만 현재 멀티 코어, 멀티 CPU, 멀티 레벨 캐시 시대에는 올바르게 작동하지 않습니다. 우선 경쟁 조건 (여러 스레드가 동시에 값을 읽을 수 있음)뿐만 아니라 가시성 문제가 발생합니다. 이 값은 " 로컬 "CPU 메모리 (일부 캐시) 에만 저장 될 수 있으며 다른 CPU / 코어 (및 스레드)에는 표시되지 않습니다. 이것이 많은 사람들이 스레드에서 변수의 로컬 사본 을 참조하는 이유 입니다. 매우 안전하지 않습니다. 이 인기 있지만 깨진 스레드 중지 코드를 고려하십시오.
private boolean stopped;
public void run() {
while(!stopped) {
//do some work
}
}
public void pleaseStop() {
stopped = true;
}
변수에 추가 volatile
하면 stopped
잘 작동합니다. 다른 스레드가 메소드 stopped
를 통해 변수를 수정 pleaseStop()
하면 작업 스레드 while(!stopped)
루프 에서 즉시 변경 사항을 볼 수 있습니다 . BTW 이것은 스레드를 중단시키는 좋은 방법이 아닙니다. 사용하지 않고 영원히 실행되는 스레드를 중지하는 방법 및 특정 Java 스레드 중지를 참조하십시오 .
AtomicInteger
private AtomicInteger counter = new AtomicInteger();
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
이 AtomicInteger
클래스는 CAS ( 비교 및 스왑 ) 저수준 CPU 작업 (동기화 필요 없음)을 사용합니다. 현재 값이 다른 값과 같고 성공적으로 반환 된 경우에만 특정 변수를 수정할 수 있습니다. 따라서 실행할 getAndIncrement()
때 실제로 루프에서 실행됩니다 (간단한 실제 구현).
int current;
do {
current = get();
} while(!compareAndSet(current, current + 1));
그래서 기본적으로 : 읽기; 증가 된 값을 저장하십시오. 성공하지 못한 경우 (값이 더 이상 같지 않음 current
) 읽고 다시 시도하십시오. 은 compareAndSet()
네이티브 코드 (조립)에서 구현됩니다.
volatile
동기화하지 않고
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
이 코드는 올바르지 않습니다. 가시성 문제를 해결하지만 ( volatile
다른 스레드가에 대한 변경 사항을 볼 수 있도록 counter
) 여전히 경쟁 조건이 있습니다. 이것은 여러 번 설명 되었습니다 : 사전 / 사후 증가는 원자 적이 지 않습니다.
유일한 부작용 volatile
은 캐시를 " 플러시 "하여 다른 모든 당사자가 최신 버전의 데이터를 볼 수 있도록하는 것입니다. 이것은 대부분의 상황에서 너무 엄격합니다. 이것이 volatile
기본이 아닌 이유 입니다.
volatile
동기화하지 않고 (2)
volatile int i = 0;
void incIBy5() {
i += 5;
}
같은 위와 문제 때문이 아니라 더 악화 i
되지 않습니다 private
. 경쟁 조건이 여전히 존재합니다. 왜 문제입니까? 예를 들어, 두 개의 스레드가이 코드를 동시에 실행하면 출력은 + 5
또는 일 수 있습니다 + 10
. 그러나 변경 내용이 표시됩니다.
여러 독립 synchronized
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
놀랍게도이 코드도 올바르지 않습니다. 사실, 그것은 완전히 잘못되었습니다. 우선, 당신이 동기화 i
하려고하고 있습니다. 변경 될 예정입니다 (더욱 i
원시적이므로 Integer
오토 박스를 통해 생성 된 임시에서 동기화하고 있다고 생각합니다 ...) 완전히 결함이 있습니다. 당신은 또한 쓸 수 있습니다 :
synchronized(new Object()) {
//thread-safe, SRSLy?
}
두 개의 스레드가 동일한 잠금으로 동일한 synchronized
블록에 들어갈 수 없습니다 . 이 경우 (및 마찬가지로 코드에서) 잠금 객체는 모든 실행마다 변경되므로 효과적으로 영향을 미치지 않습니다.synchronized
this
동기화에 최종 변수 (또는 )를 사용한 경우에도 코드는 여전히 올바르지 않습니다. 두 개의 스레드가 먼저 읽을 i
수 있습니다.temp
동 기적으로 (로컬에서 같은 값을 갖는 temp
으로, 다음 첫 번째 할당하는 새로운 값) i
(1 ~ 6 말을) 다른 하나는 (1 ~ 6) 같은 일을한다.
읽기에서 값 할당까지 동기화가 이루어져야합니다. 첫 번째 동기화는 영향을 미치지 않으며 (읽는 int
것은 원자적임) 두 번째 동기화는 영향을 미치지 않습니다 . 제 생각에는 올바른 형식입니다.
void synchronized incIBy5() {
i += 5
}
void incIBy5() {
synchronized(this) {
i += 5
}
}
void incIBy5() {
synchronized(this) {
int temp = i;
i = temp + 5;
}
}