머리말
이전 기사는 원자* 클래스를 언급 한 CAS 원칙에 대해 이야기했습니다. 원자 연산을 구현하기위한 메커니즘은 휘발성의 메모리 가시성 특성에 의존합니다. CAS와 Atomic*을 아직 모른다면, 우리가 말하는 CAS 스핀 잠금 장치를 살펴 보는 것이 좋습니다.
동시성의 세 가지 특성
우선, 휘발성을 사용하려면 다중 스레드 동시성 환경에 있어야합니다. 우리가 자주 이야기하는 동시 시나리오에는 원자력, 가시성 및 질서가있는 세 가지 중요한 특성이 있습니다. 이 세 가지 특성이 충족 될 때만 동시 프로그램을 올바르게 실행할 수 있습니다. 그렇지 않으면 다양한 문제가 발생합니다.
원자력, CAS 및 Atomic* 클래스 이전 기사에서 언급 한 클래스는 간단한 작업의 원자력을 보장 할 수 있습니다. 일부 책임있는 작업의 경우 동기화 또는 다양한 잠금 장치를 사용하여 구현할 수 있습니다.
가시성은 여러 스레드가 동일한 변수에 액세스 할 때, 한 스레드는 변수의 값을 수정하고 다른 스레드는 즉시 수정 된 값을 볼 수 있습니다.
순서, 프로그램 실행 순서는 코드 순서로 실행되며 지침은 재정렬되지 않습니다. 이것이 사실이 아니라는 것은 당연한 것 같습니다. 지시 재정렬은 JVM이 지침을 최적화하고 프로그램 운영 효율성을 향상시키고 단일 스레드 프로그램의 실행 결과에 영향을 미치지 않으면 서 가능한 한 병렬 처리를 개선하는 것입니다. 그러나 다중 스레드 환경에서 일부 코드의 순서는 논리적 부정확성을 유발할 수 있습니다.
휘발성은 가시성과 순서의 두 가지 기능을 구현합니다. 따라서 다중 스레드 환경에서는이 두 가지 기능의 기능을 보장해야하며 휘발성 키워드를 사용할 수 있습니다.
변동성이 가시성을 보장하는 방법
가시성과 관련하여 컴퓨터의 프로세서와 메인 메모리를 이해해야합니다. 멀티 스레딩으로 인해 스레드 수에 관계없이 결국 컴퓨터 프로세서에서 수행됩니다. 오늘날의 컴퓨터는 기본적으로 멀티 코어이며 일부 기계에는 다중 프로세서도 있습니다. 멀티 프로세서의 구조 다이어그램을 살펴 보겠습니다.
이것은 두 개의 프로세서 인 쿼드 코어가있는 CPU입니다. 프로세서는 실제 슬롯에 해당하고 여러 프로세서가 QPI 버스를 통해 연결됩니다. 프로세서는 여러 코어와 프로세서 간의 멀티 코어 공유 L3 캐시로 구성됩니다. 코어에는 레지스터, L1 캐시, L2 캐시가 포함되어 있습니다.
프로그램을 실행하는 동안 데이터 읽기 및 쓰기가 관련되어야합니다. 우리는 메모리 액세스 속도가 이미 매우 빠르지 만 여전히 CPU 실행 지침의 속도보다 훨씬 열등하다는 것을 알고 있습니다. 따라서 커널에서 L1, L2 및 L3 레벨 3 개의 캐시가 추가됩니다. 이러한 방식으로 프로그램이 실행될 때 필요한 데이터가 먼저 기본 메모리에서 코어 캐시로 복사되고 작업이 완료된 후 메인 메모리에 기록됩니다. 다음 그림은 레지스터에서 캐시, 메인 메모리, 하드 디스크까지 데이터에 액세스하는 CPU 액세스의 개략도입니다. 속도가 느려지고 속도가 느려집니다.
CPU 구조를 이해 한 후에는 프로그램 실행의 특정 프로세스를 살펴보고 예를 들어 간단한 자체 개수 작업을 수행하겠습니다.
i = i+1;
이 명령문을 실행할 때 코어에서 실행되는 스레드는 i의 값을 코어가있는 캐시에 복사합니다. 작업이 완료되면 메인 메모리에 다시 기록됩니다. 다중 스레드 환경에서 각 스레드는 러닝 코어의 캐시 영역에 해당 작업 메모리를 갖습니다. 그런 다음 i+1의 문제를 살펴 보겠습니다. i의 초기 값이 0이라고 가정하면이 문장을 동시에 실행하는 두 개의 스레드가 있으며 각 스레드는 다음을 수행하는 데 세 단계가 필요합니다.
1. 메인 메모리에서 스레드 작업 메모리로 I 값을 읽으십시오. 즉, 해당 커널 캐시 영역;
2. i+1의 값을 계산합니다.
3. 결과 값을 메인 메모리로 다시 쓰십시오.
두 스레드가 각각 10,000 배 실행 된 후에는 예상 값이 20,000이어야합니다. 불행히도, I의 값은 항상 20,000보다 작습니다. 이 문제의 이유 중 하나는 캐시 일관성 문제입니다. 이 예에서는 스레드의 캐시 사본이 수정되면 다른 스레드의 캐시 사본이 즉시 무효화되어야합니다.
휘발성 키워드를 사용한 후 다음 효과는 다음과 같습니다.
1. 변수가 수정 될 때마다 프로세서 캐시 (작업 메모리)가 기본 메모리에 다시 기록됩니다.
2. 작업 메모리의 기본 메모리에 다시 쓰면 다른 스레드의 프로세서 캐시 (작업 메모리)가 유효하지 않습니다.
휘발성은 메모리 가시성을 보장하기 때문에 실제로 CPU의 캐시 일관성을 보장하는 MESI 프로토콜을 사용합니다. MESI 프로토콜에는 많은 내용이 있으므로 설명하지 않겠습니다. 직접 확인하십시오. 요컨대, 휘발성 키워드가 사용됩니다. 휘발성 변수에 대한 스레드의 수정이 즉시 기본 메모리에 다시 기록되어 다른 스레드의 캐시 라인이 무효화되고 다른 스레드는 변수를 다시 사용해야합니다. 주 메모리에서 읽어야합니다.
그런 다음 휘발성으로 위의 I 변수를 수정하고 다시 실행하면 각 스레드가 10,000 번 실행됩니다. 불행히도 여전히 20,000 미만입니다. 이게 왜?
휘발성은 가시성을 보장하기 위해 CPU의 MESI 프로토콜을 사용합니다. 그러나 휘발성은이 자체 증가 작업이 세 단계로 나뉘어 있기 때문에 작업의 원자력을 보장하지는 않습니다. 스레드 1이 메인 메모리에서 i 값을 10이라고 가정하고 현재는 막힘이 발생하지만 아직 수정되지 않았다고 가정합니다. 이때 스레드 2는 메인 메모리에서 I 값을 읽습니다. 이 시점 에서이 두 스레드가 읽은 I 값은 10 개 모두 동일하며 스레드 2는 1에 1을 추가하고 즉시 기본 메모리에 다시 씁니다. 현재 MESI 프로토콜에 따르면 스레드 1의 작업 메모리에 해당하는 캐시 라인은 유효하지 않은 상태로 설정됩니다. 그러나 스레드 1은 이미 메인 메모리에서 I 값을 복사했으며 이제 1을 추가하고 메인 메모리에 다시 쓰는 작업 만 수행합니다. 두 스레드는 10을 기준으로 1을 추가 한 다음 기본 메모리에 다시 기록하므로 기본 메모리의 최종 값은 예상 12가 아닌 11입니다.
따라서 휘발성을 사용하면 메모리 가시성을 보장 할 수 있지만 원자력을 보장 할 수는 없습니다. 원자력이 여전히 필요한 경우이 이전 기사를 참조하십시오.
휘발성이 순서를 보장하는 방법
Java 메모리 모델에는 선천적 인 "OrderLine"이 있습니다. 즉, 어떤 수단도없이 보장 될 수 있습니다. 이것은 일반적으로 발생하기 전에 발생합니다. 두 작업의 실행 순서가 이전의 원칙에서 발생할 수없는 경우, 순서 성을 보장 할 수 없으며 가상 머신은 마음대로 다시 주문할 수 있습니다.
다음은 "Java Virtual Machines에 대한 심층적 인 이해"에서 발췌 한 8 가지 원칙입니다.
여기서 우리는 주로 휘발성 키워드의 규칙에 대해 이야기하고 유명한 싱글 톤 패턴에서 이중 체크인의 예를 제시합니다.
클래스 싱글 톤 {개인 휘발성 정적 싱글 톤 인스턴스 = null; private singleton () {} public static singleton getInstance () {if (instance == null) {// 1 단계 동기화 (Singleton.class) {if (instance == null) // step 2 instance = new Singleton (); // 3 단계}} 인스턴스를 반환합니다. }}인스턴스가 휘발성으로 수정되지 않은 경우 어떤 결과가 생성 될 수 있습니까? getInstance () 메소드를 호출하는 두 개의 스레드가 있다고 가정합니다. 스레드 1은 step1을 실행하고 인스턴스가 null임을 발견 한 다음 싱글 톤 클래스를 동시에 잠그는 것을 발견합니다. 그런 다음 인스턴스가 다시 Null인지 여부를 결정하고 여전히 NULL인지 확인한 다음 3 단계를 실행하고 싱글 톤을 인스턴스화하기 시작합니다. 인스턴스화 과정에서 스레드 2는 1 단계로 이동하여 인스턴스가 비어 있지 않다는 것을 알 수 있지만 현재로서는 인스턴스가 완전히 초기화되지 않을 수 있습니다.
무슨 뜻입니까? 객체는 세 단계로 초기화되며 다음 의사 코드로 표시됩니다.
메모리 = 할당 (); // 1. 객체 ctorinstance (메모리)의 메모리 공간을 할당; // 2. 객체 인스턴스 = 메모리를 초기화합니다. // 3. 물체를 가리키는 물체의 메모리 공간을 객체를 가리키십시오.
2 단계와 3 단계는 1 단계에 의존해야하고 2 단계와 3 단계에는 종속성이 없기 때문에,이 두 문장은 명령 재 교정을 겪게되거나, 즉, 2 단계 전에 3 단계가 실행될 수 있습니다.이 경우 3 단계가 실행되지 않았지만, 2 단계는 아직 실행되지 않았다. 지금은 스레드 2가 인스턴스가 NULL이 아니라고 판단하므로 인스턴스 인스턴스를 직접 반환합니다. 그러나 현재 인스턴스는 실제로 불완전한 객체이므로 사용할 때 문제가 발생합니다.
휘발성 키워드를 사용하여 "휘발성으로 수정 된 변수를 작성하는 원칙을 사용하는 것은 위의 초기화 프로세스에 해당합니다. 2 단계와 3 단계는 모두 쓰기 인스턴스이므로 인스턴스를 읽을 때 나중에 발생해야합니다. 즉, 완전히 초기화되지 않은 인스턴스를 반환 할 가능성이 없습니다.
기본 JVM은 "메모리 배리어"를 통해 수행됩니다. 메모리 펜스라고도하는 메모리 배리어는 메모리 작업에 대한 순차적 제한을 구현하는 데 사용되는 일련의 프로세서 지침입니다.
마침내
휘발성 키워드를 통해 동시 프로그래밍의 가시성과 질서에 대해 배웠습니다. 물론 간단한 이해 일뿐입니다. 더 깊이 이해하려면 반 친구들에게 의지하여 직접 공부해야합니다.
관련 기사
우리가 말하는 CAS 스핀 잠금 장치는 무엇입니까
요약
위는이 기사의 전체 내용입니다. 이 기사의 내용에 모든 사람의 연구 나 작업에 대한 특정 참조 가치가 있기를 바랍니다. 궁금한 점이 있으면 의사 소통을 위해 메시지를 남길 수 있습니다. Wulin.com을 지원 해주셔서 감사합니다.