happens-before 원칙 을 깊이 이해 하 다.

7068 단어 happens-before자바
자바 메모리 모델(JMM,자바 메모리 모델)을 볼 때 항상 곤 혹 스 럽 습 니 다.스 레 드,메 인 메모리(main memory),작업 메모리(working memory)에 대해 저 는 실제 맵 의 하드웨어 를 찾 을 수 있 습 니 다.스 레 드 는 커 널 스 레 드 에 대응 할 수 있 고 메 인 메모리 에 대응 할 수 있 습 니 다.작업 메모 리 는 쓰기 버퍼,캐 시(cache),레지스터 등 일련의 데이터 액세스 효율 을 높이 기 위 한 임시 저장 구역 을 포함 합 니 다.그러나 happens-before 원칙 이 라 고 하면'장 이 스님 은 종 잡 을 수 없다'.이것 은 전체 JMM 에서 볼 수 있 는 원칙 을 포함 하 는 규칙 입 니 다.도대체 어떻게 이해 하 는 지 개인 적 인 이 해 를 기록 하 겠 습 니 다.
두 작업 사이 에 happens-before 관 계 를 가 진 다 는 것 은 이전 작업 이 다음 작업 전에 실행 되 어야 한 다 는 것 을 의미 하지 않 습 니 다.happens-before 는 이전 작업 에 만 다음 작업 을 볼 수 있 도록 요구 합 니 다.
이 견 해 를 나 는 연이어 여러 권 의 책 에서 모두 본 적 이 있다.즉,happens-before 원칙 과 일반적인 의미 에서 의 시간 은 선후 가 다르다 는 것 이다.과연 무엇 일 까요?한 걸음 한 걸음 보다.
순서 일치 성 메모리 모델
우선 이상 화 된 모델 을 살 펴 보 겠 습 니 다.순서 일치 성(Sequentially Consistent)메모리 모델 입 니 다.이 모델 에서 모든 조작 은 프로그램의 순서에 따라 실행 되 고 모든 조작 은 원자 이 며 모든 스 레 드 를 즉시 볼 수 있 습 니 다. 
 
이 시스템 은 같은 시간 에 메모리 읽 거나 쓸 수 있 는 스 레 드 만 있 습 니 다.즉,이 시스템 의 모든 명령 간 에 엄 격 히 집행 하 는 선후 에 따라 happens-before 관 계 를 가진다.모든 스 레 드 는 일치 하 는 전역 명령 실행 보 기 를 볼 수 있 습 니 다.버스 1 을 스 레 드 와 메모리 사이 의 채널 로 본다 면 순서 일치 성 모델 은 모든 읽 기/쓰기 메모리 작업 시 버스 를 잠 그 는 것 과 같다.
특히 주의해 야 할 것 은 순서 일치 성 모델 은 다 중 스 레 드 에 동기 화 문제 가 없 는 것 이 아니 라 모든 작업 간 에 동기 화 문제 가 존재 하지 않 습 니 다.만약 에 당신 의 조작 이 여러 작업 의 집합체 라면 안전 하 게 작업 할 수 없습니다.그림 에서 보 여 주 는 것 은 흔히 볼 수 있 는 자체 증가 작업 입 니 다.두 스 레 드 는 모두 같은 실행 보기 가 있 습 니 다.1->2->3->4->5->6.그러나 스 레 드 A 의 쓰기 결 과 는 스 레 드 B 에 의 해 덮어 졌 다.A 스 레 드 읽 기와 쓰 기 는 B 스 레 드 에 즉시 볼 수 있 지만 5/6 의 쓰기 작업 이 메모리 에 미 치 는 영향 은 1/2 의 읽 기 작업 에 의존 하기 때문에 다 중 스 레 드 에 문제 가 존재 합 니 다.

분명 한 것 은 순서 일치 성 모델 은 희생 병행 도,다 중 스 레 드 로 공유 메모리 에 대한 가시 성 을 바 꾸 는 이상 적 인 모델 이다.JMM 에서 volatile 및 synchronized 의 메모리 의 미 를 실현 하 는 방식 은 바로 버스 를 잠 그 거나 스 레 드 자체 저장(working memory)을 잠 그 는 것 입 니 다.
자바 메모리 모델
자바 메모리 모델 에 관 한 책 글 은 한우충동 이 므 로 모두 가 자신의 이 해 를 가지 고 있 을 것 이다.위의 순서 일치 성 모델 로 만 JMM 을 끌 어 내 고 구체 적 인 차이 점 이 어디 에 있 는 지 살 펴 보 자.

작업 메모 리 는 순서 일치 성 메모리 모델 과 뚜렷하게 구별 되 는 곳 임 을 알 수 있다.사실 가시 적 인 문 제 를 일 으 키 는 원인 중 하 나 는 바로 이 작업 메모리(캐 시,쓰기 버퍼,레지스터 등 포함)에 있다.작업 메모리 로 인해 모든 스 레 드 는 자신의 개인 저장 소 를 가지 게 되 었 고 대부분의 시간 동안 데이터 에 대한 액세스 작업 은 이 지역 에서 이 루어 졌 다.그러나 우 리 는 데이터 가 메 인 저장 소 에 기 록 될 때 까지 데 이 터 를 작성 해 야 진정 으로 완성 할 수 있다.실제로 모든 스 레 드 는 하나의 복사 본 을 유지 하고 모든 스 레 드 는 자신의 작업 메모리 에서 공유 메모리 의 데 이 터 를 계속 읽 고 쓰 는 복사 본 입 니 다.단일 스 레 드 상황 에서 이 던 전 은 어떠한 문제 도 일 으 키 지 않 습 니 다.그러나 다 중 스 레 드 에 도착 하면 한 스 레 드 가 변 수 를 메 인 저장 소 에 기록 하고 다른 스 레 드 는 모 르 며 다른 스 레 드 의 복사 본 은 모두 만 료 됩 니 다.예 를 들 어 작업 메모리 의 존재 로 인해 프로그래머 가 쓴 코드 는 일반적인 공유 변 수 를 쓰 고 버퍼 에 먼저 쓸 수 있 습 니 다.그 명령 이 완 료 된 시간 이 늦 어 졌 습 니 다.실제 표현 은 바로 우리 가 흔히 말 하 는'명령 재 정렬'입 니 다.(이것 은 실제 적 으로 내부 저장 모델 차원 의 재 정렬 이 고 재 정렬 은 컴 파일 러 일 수도 있 습 니 다.기계 명령 등급 의 난 서.
따라서 자바 메모리 모델 에서 모든 스 레 드 는 순서 일치 모델 처럼 정확 한 명령 실행 보 기 를 가지 지 않 고 하나의 명령 이 재 정렬 될 수 있 습 니 다.한 스 레 드 의 측면 에서 볼 때 다른 스 레 드(심지어 이 스 레 드 자체)가 실행 하 는 명령 순 서 는 여러 가지 가능성 이 있다.즉,한 스 레 드 의 실행 결 과 는 다른 스 레 드 의 가시 성 을 보장 할 수 없다 는 것 이다.
가시 적 인 문 제 를 일 으 킨 원인 을 정리 하 다.
1.데이터 의 작성 은 버퍼 의 도입 과 같은 다른 스 레 드 에 알 릴 수 없습니다.
2.스 레 드 는 다른 스 레 드 가 공유 변수 에 대한 수정 을 제때에 읽 을 수 없습니다.예 를 들 어 캐 시 사용 등 입 니 다.
3.각종 등급 에서 명령 에 대한 재 정렬 으로 인해 명령 을 집행 하 는 순서 가 확정 되 지 않 는 다.
그래서 가시 적 인 문 제 를 해결 하려 면 본질 적 으로 공유 변수 에 대한 수정 을 라인 이 다른 라인 으로 제때에 동기 화해 야 한다.우리 가 사용 하 는 하드웨어 구조 에서 순서 일치 성 메모리 모델 의 전체적인 일치 하 는 명령 집행 순 서 를 갖 추 지 못 하고 명령 집행 시간 에 대해 토론 하 는 것 은 의미 가 없 거나 시간 적 인 선 후 를 확정 할 방법 이 없다.다음 프로그램 을 볼 수 있 습 니 다.각 스 레 드 의 flag 던 전 은 얼마나 후에 업 데 이 트 됩 니까?정 답 은 스 레 드 가 언제 자신의 작업 메모 리 를 새로 고 치 는 지 확인 할 수 없습니다.

public class testVisibility {
 public static boolean flag = false;

 public static void main(String[] args) {
  List<Thread> thdList = new ArrayList<Thread>();
  for(int i = 0; i < 10; i++) {
   Thread t = new Thread(new Runnable(){
    public void run() {
     while (true) {
      if (flag) {
       //      ,                 
       //      ,   Thread            flag
       //      flag  volatile       
         System.out.println(Thread.currentThread().getId() + " is true now"); 
      }
     }
    }
   });
   t.start();
   thdList.add(t);
  }

  flag = true;
  System.out.println("set flag true");

  //         
  try {
   for (Thread t : thdList) {
    t.join();
   }
  } catch (Exception e) {

  }
 }
}

그러면 우 리 는 명령 집행 의 선후 에 대해 토론 할 필요 도 없고 토론 할 필요 도 없다.우 리 는 실제 적 으로 특정한 스 레 드 의 조작 이 다른 스 레 드 에 대해 볼 수 있 는 지 알 고 싶 어서 happens-before 라 는 가시 적 원칙 을 규정 했다.프로그래머 는 이 원칙 을 바탕 으로 가시 적 인 판단 을 할 수 있다.
volatile 변수
volatile 은 happens-before 를 실천 하 는 키워드 이다.volatile 에 대한 설명 을 보면 알 수 있 습 니 다.happens-before 는 스 레 드 가 다른 스 레 드 에서 공유 변 수 를 수정 하 는 메 시 지 를 받 고 이 스 레 드 에서 공유 변 수 를 읽 는 선후 관 계 를 말 합 니 다.다시 한 번 생각해 보 세 요.happens-before 원칙 이 없 으 면 하나의 스 레 드 가 자신의 공유 변수 사본 을 읽 을 때 다른 스 레 드 가 이 변 수 를 수정 하 는 메시지 가 동기 화 되 지 않 은 것 과 같 지 않 습 니까?이것 이 바로 가시 적 인 문제 다.
volatile 변수 규칙:volatile 에 대한 쓰기,happens-before 는 임의의 후속 으로 이 volatile 변 수 를 읽 습 니 다.
스 레 드 A 는 volatile 변 수 를 쓰 는데 실질 적 으로 스 레 드 A 가 이 자 물 쇠 를 가 져 오 려 는 특정한 스 레 드 에(스 레 드 A 가 공유 변수 에 대해 수정 한)메 시 지 를 보 냈 습 니 다.
스 레 드 B 는 volatile 변 수 를 읽 습 니 다.실질 적 으로 스 레 드 B 가 이전 스 레 드 에서 보 낸(공유 변수 에 대한 수정)메 시 지 를 받 았 습 니 다.
스 레 드 A 는 volatile 변 수 를 쓰 고 그 다음 에 스 레 드 B 는 이 변 수 를 읽 습 니 다.이 과정 은 실질 적 으로 스 레 드 A 가 메 인 메모리 로 스 레 드 B 에 게 메 시 지 를 보 냅 니 다.
사실 volatile 의 실현 방식 을 자세히 살 펴 보면 실제 적 으로 정렬 의 범 위 를 제한 하 는 것 이다.메모리 장벽(Memory Barrier or Memory Fence)을 넣 는 것 이다.즉,명령 집행 을 허용 하 는 시간의 선후 순서 가 일정한 범위 내 에서 변화 하 는데 이 범 위 는 바로 happens-before 원칙 에 따라 규정 한 것 이다.메모리 장벽 은 요약 하면 두 가지 기능 이 있다.
1.쓰기 버퍼 의 내용 을 메모리 에 새로 고침 하여 다른 스 레 드/CPU 에서 볼 수 있 도록 합 니 다.
2.읽 기와 쓰기 동작 을 금지 하 는 메모리 장벽 을 넘 어 정렬 하기
이 기능 을 조합 하면 위 에서 말 한 happens-before 가 표현 한 스 레 드 통신 과정 을 완성 합 니 다.
volatile 쓰기 동작 앞 에 StoreStore 장벽 을 삽입 합 니 다.
volatile 쓰기 동작 뒤에 StoreLoad 장벽 을 삽입 합 니 다.
volatile 읽 기 동작 뒤에 LoadLoad 장벽 을 삽입 합 니 다.
volatile 읽 기 동작 뒤에 Loadstore 장벽 을 삽입 합 니 다.
메모리 장벽 의 종류 에 대해 서 는 연구 의 중점 이 아니다.다 중 프로세서 시스템 에서 이 장벽 이 어떻게 프로 세 서 를 뛰 어 넘 어 작업 수행 을 막 을 수 있 을 까?예 를 들 어 아래 의 읽 기와 쓰기 동작:

public static volatile int race = 0;
// Thread A
public static void save(int src) {
 race = src;
}
// Thread B
public static int load() {
 return race;
}
이 는 운영 체제 에서 하드웨어 차원 으로 의 관념 전환 을 언급 하고 버스 트 랜 잭 션(Bus transaction)의 개념 을 참조 할 수 있다.CPU 가 메모리 와 데 이 터 를 교환 하려 고 할 때 실제 버스 는 데이터 교환 작업 을 동기 화 합 니 다.같은 시간 에 하나의 CPU 만 읽 기/쓰기 메모리 가 있 을 수 있 기 때문에 우리 가 본 다 중 프로세서 와 병행 하고 CPU 의 계산 자원 을 병행 합 니 다.버스 에서 볼 때 저 장 된 읽 기와 쓰기 동작 은 직렬 이 고 일정한 순서에 따른다.메모리 장벽 이 프로세서 의 읽 기와 쓰 기 를 제한 하고 통신 을 완성 할 수 있 는 이유 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기