JMM - 재미 발생-전

재미 발생-전에 우리는 간략하게 아래에 몇 가지 기본 개념을 설명 할 필요가

캐시

그 계산 속도의 CPU와 메모리의 급속한 발전과 함께 읽고 읽기 및 쓰기 메모리 여전히 경우, 격차가 성장 속도를 작성 후 CPU 메모리의 처리 속도 읽고이 격차를 해소하기 위해, 속도 제한 받게됩니다 쓰기 는 CPU 캐시의 빠른 처리를 보장하기 위해 나타났다.

이는 고가의 가격에 따라 캐시에 의해 판독 및 기록 속도, 작은 용량을 특징으로한다.

CPU의 급속한 발전과 함께, 캐시는 읽기 및 쓰기 속도는 또한 또한 더 비싼 가격에 따라, 더 나은 더 빠른 프로세스 캐시의 개발에 대한 높은 요구 사항을 충족하기 위해, 상승 따라 달라집니다.

CPU 현대 컴퓨터를 내림차순으로 L1 (캐시), L2 (보조 캐시), L3 (3 단계 캐시) 사이의 처리 속도에 가까운 정도 및 순차에 따라 속도를 쓴다 그는 적어도 하나의 L1 캐시가있을 것이라고 말했다.

자바 메모리 모델

스레드 간의 통신은 (JMM)는, JMM가 주 메모리에 저장된 상기 복수의 스레드 사이에서 공유 변수를 정의하는 제어 자바 자바 메모리 모델 데이터는, 스레드들 사이의 로컬 메모리에 저장되는 각 스레드에 전용 인 로컬 메모리 저장 메인 메모리에서 멀티 스레드 공유 변수의 복사본 (로컬 메모리 버퍼의 개념을 참조하면, 존재하지 않는 가상의 개념, 레지스터 등). 도 추상 모델은 다음과 같습니다 :

어떤 것은 일 - 전

일 - 전에 개념은 원래 ( "분산 시스템 시간, 시계 및 thhe 주문의 이벤트") 레슬리 램 포트가 개발 한 종이에 광범위한 영향을 제안했다. 그것은-전에 일이 발생 분산 시스템에서 이벤트의 부분 순서로 설명했다.

JDK5는 처음부터, JSR-133를 사용하여 자바 메모리 모델은 JSR-133 일-전에 단일 스레드 또는 다중 스레드 메모리 가시성 보장을 제공 할 수의 개념을 사용합니다.

그것은 발생-전에 메모리는 프로그래머를위한 여러 스레드 사이에 가시성을 제공합니다

발생-하기 전에 다음과 같은 규칙

  • 프로그램 순서는 규칙 : 각 작업 스레드가 발생-전에 스레드의 후속 작업
  • 잠금 규칙 모니터 : 스레드 또는 다른 스레드의 스레드 잠금에 연속하여 발생-전에 개체의 잠금 해제를
  • 휘발성 변수 규칙 : 이후에이 휘발성 영역을 읽기 전에 발생 - 휘발성 필드를 작성하는
  • 이행 성 규칙 : A가 B-전에 일어나는 경우, B가 발생-전에 C는 다음이 발생-전에 C

이 규칙에 따르면 우리가 처음 여기에 정의 출시에 대한 자세한 분석 다음 스레드 간의 메모리의 가시성을 보장 할 수

가시성 메모리

그렇게-전에 위의 메인 메모리, 우리는 다음과 같은 정의에 무엇입니까 볼 것을, 메모리의 단일 스레드 또는 다중 스레드, 다음 가시성을 보장하기 위해 가시성을 제공하는 것입니다

힙 메모리는 스레드 스택 메모리는 개인이며, 스레드간에 공유됩니다. 데이터 메모리 힙 메모리의 가시성, 스택 메모리가 아닌 메모리 가시성에 미치는 영향의 문제가 있습니다.

메모리의 가시성 : 사실, 멀티 스레드 잘못이있을 수있다 또한 올바른 될 가능성이 공유 메모리의 데이터 상태를 볼 수 (물론, 우리의 목표는 메모리의 정확한 가시성을 보장하는 것입니다).

이제 우리는 메모리 가시성 문제 (즉, 어떤 상황에서,이다, 잘못된 가시성 상태 메모리는 멀티 스레드 프로그램 액세스 오류로 이어질 것) 할 때 지침이 나타납니다 어떻게 분석해야

캐시 메모리 가시성 문제가 발생

우리는 각각의 CPU는 캐시 (스레드 개인 메모리를 JMM에 대응) 프로세서가 데이터를 기록 할 수 있기 때문에, 자체 캐시, 다음 컴퓨터에 둘 이상의 CPU, 데이터 읽기 및 쓰기 시간을 가지고있다 것을 알고있다 및 즉시 데이터를 읽고 쓸 수있는 CPU의 복수 간의 불일치 초래 메모리 (메인 메모리 JMM 추상적 모델)에 브러시되지 캐시 다음

class Test {
    int val = 0;
    void f() {
        val = val + 1;
        // ...
    }
}
复制代码

그림은 올바른있을 수있다, 하나 개의 가능한 오류 상태 불확실성은 스레드 동기화가

  • 시간 T1에서, 메인 메모리로드 브로 = 0 개인 작업 메모리 실행 스레드, 그 프로세서 시간 T2 + 1을 처리하고, T3 시간은 로컬 캐시에 기입되고, T6의 시간은 로컬 캐시 전에 메인으로 플러시 기억
  • 시간 T4, 스레드 B가 실행 메인 메모리 또는를 찾을 발 = 0 (데이터가 아직 메인 메모리와 같은 브러쉬를 스레드되지 않기 때문에), 다음의 + 1이 돌아 오면 작업 메모리의 발 = 1 스레드 B를 처리를 계속
  • 화이트 최종 브러쉬 - 메모리 데이터 발 = 1

의도를 알 수 프로그래머 작업 브로 + 1을 수행하는 두 개의 스레드를 사용하는 각각의 결과가 얻어지는 발 = 2 개 결과는 결과가 실행을 완료 얻어진 발 = 1

명령 재정렬 메모리 가시성 문제가 발생

의 명령 재정렬에서 무엇인지 살펴 보자

    void f() {
        int a = 1;
        int b = 2;
        int c = a + b;
    }
复制代码

컴파일러 또는 프로세서를 재정렬 한 후, 실행 순서는 변경 될 수 수행 b = 2수행 a = 1C를 상기 단계 (2)는, 후술한다 전에 행 불가능하다.

명령을 재정렬하는 프로세서 명령들을 재정렬하는 재정렬 컴파일러 지시어들로 분할된다.

컴파일러 및 명령 실행 및 명령 재정렬의 병렬 처리 수준을 향상시키기 위해 프로세서는, 그들의 목적은 프로세스 속도를 가속화 할 수 있지만 문제 재정렬 최종 단일 스레드 실행 결과는 변경할 수 없습니다 없도록해야하는,하지만 경우입니다 잘못된 실행의 결과가 경우에 할 수있다, 그래서 멀티 스레딩의 경우, 보장 할 수 없습니다.

최종 단일 스레드 프로그램의 정확성을 보장하기 위해, 한가지 확실한 동작 사이의 의존성이있는 경우, 컴파일러 또는 프로세서 중 하나가 순서 재정렬을 수행 할 것이며,이 지금 컴파일러 처리 인 그것은 실현된다. 다음과 같이

    void f() {
        int a = 1;
        // 这个操作依赖上一步操作 a = 1,所以他们不会被重排序
        int b = a + 1;
    }
复制代码

명령이 재 배열 및 가시성은 메모리 문제로 연결하는 방법? 우리는 예를 살펴

class Test {
    private static Instance instance;
    
    public static Instance getInstance() {
        if (instance != null) {
            synchronized(Test.class) {
                if (instance != null) {
                    // 错在这里
                    instance = new Instance();
                }
            }
        }
        return instance;
    }
}
复制代码

이것은 단일 모델 (잘못된)의 공통 재확인 잠금, 그것은 반환 초기화되지 않은 경우에 발생할 수 명령 재정렬에서 잘못된 것입니다, 우리는 이유를 분석해야합니다.

인스턴스 = 새 인스턴스 (); 실제로 해체 프로세서에 의해 실행되는 경우, 다음과 같이 수행 슈도 단계는

// 步骤1 分配内存空间
memory = allocate();
// 步骤2 初始化对象
ctorInstance(memory);
// 步骤3 设置对象的内存地址
instance = memory;
复制代码

이 명령을 수있는, 우리가 객체를 초기화 할 수 있습니다 그에게 그 내용을 제공하기 위해 주소를 설정할 수 있습니다, 우리는 2 단계에 대한 단일 스레드 시나리오에서 위의 세 단계를 볼 수 있으며,이 두 가지의 3 종속되지 않습니다 다음과 같이 재정렬 :

// 步骤1 分配内存空间
memory = allocate();
// 步骤2 设置对象的内存地址
instance = memory;
// 步骤3 初始化对象
ctorInstance(memory);
复制代码

다중 스레드 시나리오 그래서, A는 2 (초기화되지 않음) 단계로 실행 그냥 메인 메모리에 작업 메모리를 새로 스레드, 스레드 B는 인스턴스를보고,에, 직접적인 수익을 초기화하고, 생성 된 생각 B는 얻을 수있는 스레드가 초기화되지 않은 객체, 후속 사용 문제가 발생할 것입니다 원인이 될 수 있습니다.

가시성 문제 메모리 해결

메모리 문제의 가시성을 주도 이러한 이유는 예기치 않은 상황이 멀티 스레드 시나리오에서 발생할 수있는 것은, 우리는 우리는 메모리 정확성의 가시성을 보장 오른쪽 결과 우측 멀티 스레드 프로그램 실행을 얻을 수 있습니다.

메모리의 가시성의 정확성을 보장하기 위해 주로 다음과 같은 기술을 통해 달성 될를

  • 휘발성 물질
    • 시인성 문제와 명령 메모리 재정렬 해결
  • 결정적인
  • 모니터 잠금
    • 가시성 문제 메모리 해결
    • 잠금 사이에 독점 액세스
  • 전에 발생
    • 전에 발생 멀티 스레드 프로그램의 수행의 정확성을 보장하기 위해 상기 세 개 이상의 기술을 이용하여 규칙을 바인딩. (즉, 올바른 결과를 얻을 수 있는지 계산할 수있다) 우리는 또한 단일 스레드 실행과 일치하는 결과를 생산이 인간이 만든 규칙과 코드는 멀티 스레드 프로그램의 존재에 결과 여부에 대응하여 알아낼 수

휘발성 물질

휘발성 변수에 시간을 쓸 때, JMM 가야 메인 메모리에 플러시 로컬 메모리 공유 변수 값을 해당 스레드 것입니다.

휘발성 두 가지 속성

  • 가시성 : 휘발성이 항상 thread가이 휘발성 마지막 쓰기 볼 수 읽기
  • 자성 : 단일 변수 휘발성 판독하는 / 가지는 원자 물품, 예를 들어, 64 비트 길이의 두 번 등

컴파일러를 들어, 휘발성 읽기 / 쓰기 순서 변경을 제한함으로써 JMM은 다음과 같은 휘발성 재 배열 규칙을 개발

재정렬 할 수 제 2 동작
제 1 동작 보통 읽기 / 쓰기 휘발성 읽기 휘발성 쓰기
보통 읽기 / 쓰기 아니
휘발성 읽기 아니 아니 아니
휘발성 쓰기 아니 아니

테이블에서 그것은 결론을 내릴 수있다 :

  • 두 번째 작업이 글을 쓰는 휘발성 시간에 상관없이 작업이 다시 정렬되지 수없는 것을
  • 두 번째 작업은 읽기 쓰기 작업에 휘발성 읽기 것은 순서를 변경하는 것이 일반적이다
  • 두 번째 읽기 및 일반 만 휘발성하지 읽기 재정렬에 대한 쓰기 작업을 할 때

그들은 그가 그것을 않는 이유는, 우리가 한 측면을 생각하기 시작 모르기 때문에 약간의 현기증을 신경 쓰지 이들 규칙을 읽은 후, 그입니다.

휘발성 변수를 작성할 때 해당 스레드 로컬 메모리가 메모리에 변수 값을 새로 고치시기가있는 경우 하나 이상의 작업이 시간 이전에 변수 전에, 모든 변경이 공유 휘발성 쓰기를 쓴 의미입니다 것입니다 모든 공유 변수는 메인 메모리로 플러시하는이 기능은 특히 중요 기분이 안됩니다!

후자의 내용을 읽은 후 그것을 할 정도로 바닥에 침몰 이유를 이해이 표에서 볼 수있을 것입니다.

우리는 지금 인해 명령 재정렬 리드에 잘못된 예 하나의 예를하기 전에 다시 보이지만, 우리는 올바른 것을 보장하는 것입니다 프로그램을 다음과 같이 변경합니다

class Test {
    private static volatile Instance instance;
    
    public static Instance getInstance() {
        if (instance != null) {
            synchronized(Test.class) {
                if (instance != null) {
                    // 错在这里
                    instance = new Instance();
                }
            }
        }
        return instance;
    }
}
复制代码

당신은 다시 정렬 할 수없는 우리가 테이프 아의 다음 조각을 보장 할 수 있습니다 후 추가, 휘발성의 추가를 볼 수 있고, 의식은 단계에 1 -> 2 -> 3 수행하고,이 것을 보장하기 위해 정확성의 단일 모델.

// 步骤1 分配内存空间
memory = allocate();
// 步骤2 初始化对象
ctorInstance(memory);
// 步骤3 设置对象的内存地址
instance = memory;
复制代码

그런 다음 컴파일러는 또한 컴파일러가의 휘발성 순서 변경을 제한하는 어떤 기술 규칙의 재 배열이라고 말했다 않습니다이 규칙을 달성하는 방법이다.

컴파일러는 바이트 코드는 프로세서 재정렬 특정 유형을 억제하는 명령어 시퀀스 메모리 배리어에 삽입되는 생성 할 때, 최적의 전략의 삽입이 너무 복잡 장벽 달성하기 위해 거의 어렵다 JMM 보수적 인 전략이 삽입 된 메모리 배리어를 따라 있도록

  • 상기 휘발성 기록 동작 각각에 장벽을 삽입 StoreStore
  • 다시 쓰기 작업은 휘발성 장벽 StoreLoad의 각각에 삽입
  • 각각의 작업을 읽은 후 휘발성 LoadLoad 장벽을 삽입
  • 각각의 작업을 읽은 후 휘발성 LoadStore 장벽을 삽입

메모리 장벽은 아래에 설명되어 있습니다

이 전략에 따라이 전략은, 우리는 그림은 휘발성 쓰기 장면입니다 참조 모든 프로세서 플랫폼, 어떤 프로그램이 의미 론적 메모리 volaile 권리를 얻을 수 있도록 할 수 있습니다

StoreStore은 위의 일반 쓰기의 모든 휘발성 쓰기 전에 메인 메모리에 플러시 있음을 확인합니다.

이 방법의 끝에 휘발성 위 StoreLoad, 그것을 읽고 정말 읽고 휘발성 삽입 StoreLoad 장벽 아래 두 경우 모두 작성해야합니다 휘발성 또는 다시 방법의 끝에 기록 된 경우, 그래서 휘발성 쓰기 메서드를 호출가 있는지 여부를 확인하기 어려운 경우.

메모리 방법을 요약 :

  • StoreStore 장벽 : 스토어는 저장 수단을 가지고 StoreStore가 저장 될이 필요, 일반 메모리의 앞에 휘발성 쓰기, 메인 브러시로 첫 걸음을 쓰기 자체를 브러시뿐만 아니라 화이트 메모리는 "저장 및 스토어"를 StoreStore 것이 아니다 의미
  • StoreLoad 장벽 : 전자는 필요 휘발성 저장하고, 후속 작업이 가정을 읽을 때, 불확실한 휘발성 쓰기 나 읽기 수 있습니다, 우리는 위 휘발성를 재정렬 읽기 - 쓰기 아래 휘발성 쓰기에 대한 StoreLoad 장벽을 삽입

다음은 휘발성 읽기 장면입니다

뿐만 아니라 방법의 요약으로

  • LoadLoad 장벽 : 2로드 휘발성 읽기를 금지하고 다음과 같은 일반적인 재정렬을 모두 읽을 필요를 의미한다
  • LoadStore 장벽 : 저장 후 첫 번째로드는 휘발성 읽기를 억제하고 다음 일반을 모두 쓸 수하기위한 것입니다

그럼 우리가 분석하는 휘발성 전에 발생 규칙과 코드를 보면

class Test {
    int num = 10;
    boolean volatile flag = false;
    
    void writer() {
        num = 100;     // 1
        flag = true;   // 2
    }
    
    void reader() {
        if (flag) {   // 3
            System.out.println(num);   // 4
        }
    }
}
复制代码

스레드가 라이터 방법, 상기 방법을 수행하도록 실행 리더 스레드 B에 걸쳐 수행 한 후 가정하자. (규칙 위의 간단한 모양을 잊어)

  • 우리는 하나의 스레드에 대한 모습 때문에 프로그램 순서는 규칙
    • 스레드 A의 경우, 1 일-전에 2
    • 스레드 B의 경우, 3 일-전에 4
  • 변수 규칙 휘발성이 발생-전에 3
    • 여기에 3 우리가 나중에 다음과 같은 일반 독자가 위에 가서 다시 정렬되지 않도록하기 위해 LoadLoad 및 LoadStore 장벽을 삽입 할 것을 알고, 휘발성 읽기입니다
    • 여기에 34 만 보장 민 플래그 = TRUE 값을보고, 재정렬되지
  • 연결 규칙 1은 발생하기 전에 2,2-전에 발생 -3,3- 발생-4 전에, 다음 1-발생 전에 4를 얻을 수있다

마지막으로, 관련하여 다음 사항을 강조하기 위해이 휘발성이 장벽은 반드시 완전히 필요에 따라, 컴파일러는 메모리 장벽을 삽입하지 않았을 시간을 삽입 할 필요가 없습니다 발견 최적화를 삽입 할 필요는 없지만, 그것을 보장 할 수 쓰기 읽기 우리는이 장벽을 삽입 올바른 결과를 얻을 수있는 방법으로, 여기 수행되지.

모니터 잠금

코드 블록 또는 메소드를 잠금 들어, 그들은 스레드가 잠금을 해제, 실행 상호 배타적 인 다른 스레드가 잠금이 후 수행 할 수 있습니다.

그것은 유사한 휘발성 메모리와 의미를 가지고

스레드가 잠금을 해제 할 때, 메인 메모리에 플러시 될 로컬 메모리 공유 변수를 해당 스레드를 숨 깁니다.

스레드가 잠금을 획득 할 때, 현재의 thread 코드는 메인 메모리에서 새 공유 변수를 취득해야합니다 모니터 임계 영역을 보호하도록 해당 로컬 메모리가 무효 설정 JMM 것이다.

의 코드 조각을 살펴 보자

    int a = 0;
    
    public synchronized void writer() {  // 1
        a++;  // 2
    }  // 3
    
    public void synchronized reader() { // 4
        int i = a; // 5
    } // 6
复制代码

가정하자 스레드 A를 실행 작가 () 스레드 B의 방법은 분석을 계속 리더 () 메소드를 실행 일 - 전

  • 프로그램 순서 규칙 (모니터 잠금이 상기 임계 분석 섹션 그래서 임계 영역 다단계 분석에 관한 때문에)
    • 线程 A, 1-발생 전에 2,2-이 발생하기 전에 3
    • 线程 B, 4-발생하기 전에 발생 5,5-전에 6
  • 잠금 규칙을 모니터, 3 일-전에 4
    • 새로 고침 메인 메모리에 갈 때 스레드가 잠금을 해제
    • B 스레드가 로크를 획득하는 경우, 로컬 메모리에 대응한다 JMM 현재 스레드가 무효로 설정되어 있기 때문에, 새 메인 메모리 공유 변수 A를 얻었다 = 1
  • 전송 규칙은 1 일-전에 2, 2 일-전에 3,3 일이 발생하기 전에-4,4 일이 발생-전에 5,5 일 - 전에 6. 마지막으로 발생-전에 2 5, 내가 적절하게 할당 할 수 있도록 얻을.

순차 일관성 모델

순차 일관성 모델 JMM은 순차 일관성 모델을 참조하여 설계되었다.

우리는 순차적 일관성 모델의 정의를 보면

  • 모든 작업은 순서대로 스레드에서 수행해야합니다
  • 무관하게 스레드 사이의 동기화 여부, 모든 스레드는 동일한 실행 순서를 볼 수 있으며, 각 동작은 모든 스레드를 수행 원자 즉시 표시해야

제 JMM 차이 시점 우리가 쉽게 볼 수 있다는 생각이 JMM은 재정렬을 명령 할 수 있으며, 실행 순서는 변경 될 수 있지만, 수득 된 최종 결과는 동일하다.

순차 일관성 모델의 절차에 동기화되지 않은 것은 그러한입니다

순차 일관성 모델이 효과를 달성해야한다 동기화의 모델이 아주 작은 의미 사실이며, 왜 필요? 최종 결과가 동기화되지 않은 경우에도 프로그램이 불확실하기 때문에이 효과는 얻을 수있다. JMM은 디자인에서 그렇게하지 않았다. 특히 우리가 어떻게하기 전에 상세한 분석 후 않아도됩니다.

스레드가 데이터를 볼 수있는 보안 멀티 스레드 가장 최소한의 동기화에 대한 JMM은 기본 값 또는 서면 다른 스레드의 값 중 하나입니다.

마지막으로, 또한이 마지막 최종 의미 메모리가 발생하는 가시성 문제의 기억 때문에 다시 혼자 쓰기에는 너무 긴 공간.

나는 << 자바 병행 프로그래밍 아트 >> 볼 때마다 다른 느낌, 자신의 이해를 깊게 아래 기사를 작성하는 그들의 생각과 함께이 시간을 가지고있다.

참조 :

  • 예술 자바 병행 프로그래밍

추천

출처juejin.im/post/5d92ad1be51d4577ec4eb955