현재 자바 스레드 동시 처리에서는 휘발성 키워드 사용에 대해 많은 혼란이 있다. 이 키워드를 사용하면 멀티 스레드 동시 처리를 수행할 때 모든 것이 잘 될 것으로 생각된다.
Java 언어는 스레드 동시성 문제를 해결하기 위해 언어 내부에 동기화된 블록 및 휘발성 키워드 메커니즘이 도입되었습니다.
동기화됨
동기화된 키워드를 통해 구현되는 동기화된 블록에는 누구나 익숙합니다. 동기화된 문과 블록 문을 추가하면 여러 스레드에서 액세스할 때 하나의 스레드만 동기화되고 수정된 메서드나 코드 블록을 동시에 사용할 수 있습니다.
휘발성 물질
휘발성으로 수정된 변수의 경우 스레드가 변수를 사용할 때마다 수정된 변수 값을 읽습니다. 휘발성은 원자적 작업을 수행하는 데 쉽게 오용될 수 있습니다.
아래 예를 살펴보겠습니다. 스레드가 시작될 때마다 카운터 inc 메서드가 호출되어 카운터에 1을 추가합니다.
실행 환경 - jdk 버전: jdk1.6.0_31, 메모리: 3G CPU: x86 2.4G
다음과 같이 코드 코드를 복사합니다 .
공개 클래스 카운터 {
공개 정적 정수 개수 = 0;
공개 정적 무효 inc() {
//결과를 명확하게 만들기 위해 여기서 1밀리초를 지연합니다.
노력하다 {
Thread.sleep(1);
} 잡기(InterruptedException e) {
}
카운트++;
}
공개 정적 무효 메인(String[] args) {
//동시에 1000개의 스레드를 시작하여 i++ 계산을 수행하고 실제 결과를 확인합니다.
for (int i = 0; i < 1000; i++) {
새로운 스레드(새로운 Runnable() {
@보수
공개 무효 실행() {
카운터.inc();
}
}).시작();
}
//여기의 값은 실행될 때마다 다를 수 있습니다(예: 1000).
System.out.println("실행 결과: Counter.count=" + Counter.count);
}
}
실행 결과: Counter.count=995
실제 작업 결과는 매번 다를 수 있습니다. 이 컴퓨터의 결과는 다음과 같습니다. 실행 결과: Counter.count=995 멀티 스레드 환경에서 Counter.count는 결과가 1000이 될 것으로 예상하지 않습니다.
많은 사람들은 이것이 다중 스레드 동시성 문제라고 생각합니다. 이 문제를 피하려면 변수 개수 앞에 휘발성을 추가하기만 하면 됩니다. 그런 다음 결과가 기대에 부합하는지 확인합니다.
다음과 같이 코드 코드를 복사합니다 .
공개 클래스 카운터 {
공개 휘발성 정적 int 개수 = 0;
공개 정적 무효 inc() {
//결과를 명확하게 만들기 위해 여기서 1밀리초를 지연합니다.
노력하다 {
Thread.sleep(1);
} 잡기(InterruptedException e) {
}
카운트++;
}
공개 정적 무효 메인(String[] args) {
//동시에 1000개의 스레드를 시작하여 i++ 계산을 수행하고 실제 결과를 확인합니다.
for (int i = 0; i < 1000; i++) {
새로운 스레드(새로운 Runnable() {
@보수
공개 무효 실행() {
카운터.inc();
}
}).시작();
}
//여기의 값은 실행될 때마다 다를 수 있습니다(예: 1000).
System.out.println("실행 결과: Counter.count=" + Counter.count);
}
}
실행 결과: Counter.count=992
예상대로 실행 결과가 여전히 1000이 아닙니다. 이유를 아래에서 분석해 보겠습니다.
Java Garbage Collection 기사에서는 JVM 런타임 중 메모리 할당에 대해 설명합니다. 메모리 영역 중 하나는 jvm 가상 머신 스택입니다. 각 스레드는 실행 중일 때 스레드 스택을 갖습니다. 스레드가 객체의 값에 접근할 때, 먼저 객체 참조를 통해 힙 메모리에 해당하는 변수의 값을 찾은 다음, 힙 메모리 변수의 특정 값을 스레드 로컬 메모리에 로드하여 복사본을 생성합니다. 그 이후에는 스레드가 힙 메모리에 있는 개체의 변수 값과 관련이 없으며 대신 수정 후 특정 순간(스레드가 종료되기 전)에 복사 변수의 값을 직접 수정합니다. 스레드 변수 copy의 값은 자동으로 힙의 개체 변수에 다시 기록됩니다. 이런 방식으로 힙에 있는 개체의 값이 변경됩니다. 아래 그림
이 상호작용에 대해 설명하세요.
읽기 및 로드는 주 메모리에서 현재 작업 메모리로 변수를 복사합니다.
실행 코드 사용 및 할당, 공유 변수 값 변경
작업 메모리 데이터로 주 메모리 관련 콘텐츠 저장 및 쓰기 새로 고침
여기서 use 및 할당은 여러 번 나타날 수 있습니다.
그러나 이러한 작업은 원자적이지 않습니다. 즉, 읽기 로드 후 주 메모리 개수 변수가 수정되면 스레드 작업 메모리의 값은 로드되었으므로 변경되지 않으므로 계산 결과는 예상대로가 됩니다. 같은
휘발성으로 수정된 변수의 경우 JVM 가상 머신은 메인 메모리에서 스레드 작업 메모리로 로드된 값이 최신인지만 보장합니다.
예를 들어 스레드 1과 스레드 2가 읽기 작업과 로드 작업 중에 주 메모리의 카운트 값이 모두 5인 것을 발견하면 최신 값을 로드합니다.
스레드 1 힙 개수가 수정된 후 주 메모리에 기록되고 주 메모리의 개수 변수는 6이 됩니다.
스레드 2는 이미 읽기 및 로드 작업을 수행했기 때문에 작업을 수행한 후 주 메모리 카운트 변수 값도 6으로 업데이트합니다.
결과적으로 휘발성 키워드를 사용하여 두 개의 스레드가 시간에 맞춰 수정된 후에도 여전히 동시성이 유지됩니다.