자바 동기 화 volatile 분석

10956 단어 자바volatile
문제.
(1)volatile 은 어떻게 가시 성 을 보장 합 니까?
(2)volatile 은 어떻게 정렬 을 금지 합 니까?
(3)volatile 의 실현 원리?
(4)volatile 의 결함?
간단 한 소개
volatile 은 자바 가상 컴퓨터 가 제공 하 는 가장 가 벼 운 동기 화 메커니즘 이 라 고 할 수 있 지만 정확하게 이해 되 지 않 아 많은 사람들 이 이 를 사용 하 는 것 에 익숙 하지 않 고 다 중 스 레 드 문제 에 부 딪 히 면 synchronized 나 다른 자 물 쇠 를 사용 하여 해결한다.
volatile 의 의 미 를 이해 하 는 것 은 다 중 스 레 드 의 특성 을 이해 하 는 데 중요 한 의 미 를 가지 기 때문에 동 형 은 volatile 의 의미 가 도대체 무엇 인지 설명 하 는 글 을 한 편 썼 다.
가시 성
앞에서 자바 메모리 모델 을 소개 할 때 우 리 는 가시 성 이란 한 라인 이 공유 변수의 값 을 수정 하면 다른 라인 이 이러한 변 화 를 즉시 감지 할 수 있다 는 것 을 말한다.
자바 메모리 모델 에 대한 설명 은 참고 하 시기 바 랍 니 다[자바 동기 화 JMM(Java Memory Model)을 자세히 이야기 합 니 다.
일반 변 수 는 이 점 을 즉시 감지 할 수 없습니다.변수의 값 이 스 레 드 간 에 전달 되 는 것 은 모두 메 인 메모리 로 이 루어 져 야 합 니 다.예 를 들 어 스 레 드 A 는 일반 변수의 값 을 수정 한 다음 에 메 인 메모리 에 다시 써 야 합 니 다.다른 스 레 드 B 는 온라인 스 레 드 A 의 재 작성 이 완 료 된 후에 메 인 메모리 에서 변수의 값 을 읽 어야 새로운 변수의 값 을 읽 을 수 있 습 니 다.즉,새로운 변수 만 스 레 드 B 를 볼 수 있 습 니 다.
이 기간 에 일치 하지 않 는 상황 이 발생 할 수 있 습 니 다.예 를 들 어:
(1)스 레 드 A 는 수정 이 완 료 된 후에 바로 다시 쓰 는 것 이 아니다.

(선로 A 는 변수 x 의 값 을 5 로 수 정 했 지만 다시 쓰 지 않 았 습 니 다.라인 B 가 주 메모리 에서 읽 은 값 은 0 입 니 다)
(2)스 레 드 B 는 메 인 메모리 에서 즉시 값 을 읽 는 것 이 아니 라 자신의 작업 메모리 에 있 는 값 을 사용 하고 있 습 니 다.
 
(스 레 드 A 는 변수 x 의 값 이 5 에서 메 인 메모리 에 되 돌 아 왔 지만 스 레 드 B 는 메 인 메모리 의 값 을 읽 지 못 하고 오래된 값 0 을 사용 하여 연산 하고 있 습 니 다)
상기 두 가지 상황 을 바탕 으로 하기 때문에 일반 변 수 는 이 점 을 즉시 감지 할 수 없다.
그러나 volatile 변 수 는 이 점 을 즉시 감지 할 수 있다.즉,volatile 은 가시 성 을 확보 할 수 있다.
자바 메모리 모델 에 따 르 면 volatile 변 수 는 수정 할 때마다 메 인 메모리 에 즉시 기록 해 야 합 니 다.volatile 변 수 는 사용 할 때마다 메 인 메모리 에서 최신 값 을 새로 고 쳐 야 합 니 다.
 
volatile 의 가시 성 은 아래 의 예 시 를 통 해 나 타 낼 수 있다.

public class VolatileTest {
 // public static int finished = 0;
 public static volatile int finished = 0;

 private static void checkFinished() {
 while (finished == 0) {
  // do nothing
 }
 System.out.println("finished");
 }

 private static void finish() {
 finished = 1;
 }

 public static void main(String[] args) throws InterruptedException {
 //            
 new Thread(() -> checkFinished()).start();

 Thread.sleep(100);

 //     finished    1
 finish();

 System.out.println("main finished");

 }
}
위의 코드 에서 finished 변 수 를 대상 으로 volatile 수식 을 사용 할 때 이 프로그램 은 정상적으로 끝 날 수 있 습 니 다.volatile 수식 을 사용 하지 않 을 때 이 프로그램 은 영원히 끝나 지 않 습 니 다.
volatile 수식 을 사용 하지 않 을 때 checkFinished()가 있 는 스 레 드 는 매번 자신의 작업 메모리 에 있 는 변수의 값 을 읽 기 때문에 이 값 은 0 이 므 로 while 순환 에서 벗 어 나 지 않 습 니 다.
volatile 수식 을 사용 할 때 checkFinished()가 있 는 스 레 드 는 매번 메 인 메모리 에서 최신 값 을 불 러 옵 니 다.finished 가 메 인 스 레 드 에 의 해 1 로 수정 되면 바로 감지 되 고 while 순환 에서 벗 어 납 니 다.
정렬 금지
앞에서 자바 메모리 모델 을 소개 할 때 우 리 는 자바 의 질서 성 은 한 마디 로 요약 할 수 있다 고 말 했다.만약 에 이 스 레 드 에서 관찰 하면 모든 조작 이 질서 가 있다.다른 스 레 드 에서 관찰 하면 모든 조작 이 무질서 하 다.
전반 문 은 스 레 드 내 에서 직렬 로 나타 나 는 의 미 를 말 하 며,후반 문 은'명령 재 정렬'현상 과'작업 메모리 와 메 인 메모리 동기 화 지연'현상 을 말한다.
일반 변 수 는 이 방법 을 실행 하 는 과정 에서 할당 결과 에 의존 하 는 모든 곳 에서 정확 한 결 과 를 얻 을 수 있 을 뿐 변수 할당 작업 의 순서 가 프로그램 코드 의 실행 순서 와 일치 하 는 것 을 보장 할 수 없습니다.하나의 스 레 드 방법 을 실행 하 는 과정 에서 이 점 을 알 수 없 기 때 문 입 니 다.이것 이 바로'스 레 드 내 에서 직렬 로 나타 나 는 의미'입 니 다.
예 를 들 어 다음 코드:

//          
int i = 0;
int j = 1;
위의 두 마디 는 의존 관계 가 없습니다.JVM 은 실행 할 때 CPU 의 처리 능력 을 충분히 이용 하기 위해 int j=1 을 먼저 실행 할 수 있 습 니 다.이 말 은 다시 정렬 한 것 이지 만 온라인 상에 서 는 감지 할 수 없다.
아무런 영향 이 없 는 것 같 지만 다 중 스 레 드 환경 에서 라면?
우 리 는 예 를 하나 더 보 자.

public class VolatileTest3 {
 private static Config config = null;
 private static volatile boolean initialized = false;

 public static void main(String[] args) {
 //   1         
 new Thread(() -> {
  config = new Config();
  config.name = "config";
  initialized = true;
 }).start();

 //   2                 
 new Thread(() -> {
  while (!initialized) {
  LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
  }

  // do sth with config
  String name = config.name;
 }).start();
 }
}

class Config {
 String name;
}
이 예 는 간단 합 니 다.스 레 드 1 은 설정 을 초기 화 하 는 것 을 책임 집 니 다.스 레 드 2 는 설정 이 초기 화 되 었 음 을 감지 하고 설정 을 사용 하여 일 을 합 니 다.
이 예 에서 initialized 가 volatile 을 사용 하지 않 으 면 정렬 을 다시 할 수 있 습 니 다.예 를 들 어 설정 을 초기 화하 기 전에 initialized 의 값 을 true 로 설정 하면 스 레 드 2 가 이 값 을 true 로 읽 고 설정 을 사용 하면 오류 가 발생 할 수 있 습 니 다.
(이 예 는 정렬 을 다시 하 는 것 을 설명 하 는 데 사 용 될 뿐 실제 실행 시 나타 나 기 어렵 습 니 다.)
이 예 를 통 해 동 형 은 사람들 이'이 라인 에서 관찰 하면 모든 조작 이 질서 가 있다.다른 라인 에서 관찰 하면 모든 조작 이 무질서 하 다 는 것 을 더욱 깊이 이해 할 수 있다.
따라서 재 정렬 은 다른 스 레 드 의 시각 에 서 있 는 것 입 니 다.이 스 레 드 에 서 는 재 정렬 의 영향 을 감지 할 수 없 기 때 문 입 니 다.
한편,volatile 변 수 는 정렬 을 금지 하고 프로그램의 실제 운행 이 코드 순서에 따라 실 행 될 수 있 도록 보장 합 니 다.
구현:메모리 장벽
volatile 은 가시 성과 정렬 금 지 를 보장 할 수 있다 고 했 는데 어떻게 이 루어 졌 을 까?
정 답 은 메모리 장벽 이다.
메모리 장벽 은 두 가지 작용 이 있다.
(1)장벽 양쪽 의 명령 을 막 고 정렬 하기;
(2)버퍼/캐 시 에 있 는 데 이 터 를 메 인 메모리 에 강제로 기록 하여 캐 시 에 있 는 데 이 터 를 무효 화 합 니 다.
'메모리 장벽'에 대한 지식 은 각 분야 의 신 들 의 관점 도 완전히 일치 하지 않 기 때문에 여기 서도 설명 을 하지 않 습 니 다.관심 이 있 는 사람 은 아래 의 글 을 볼 수 있 습 니 다.
(1)Doug Lea 의'The JSR-133 Cookbook for Compiler Writers'
http://g.oswego.edu/dl/jmm/cookbook.html
Doug Lea 는 자바 와 가방 을 보 낸 작가,황소!
(2)Martin Thompson 의《Memory Barriers/Fences》
https://mechanical-sympathy.blogspot.com/2011/07/memory-barriersfences.html
Martin Thompson 은 성능 을 극 대화 하 는 데 전념 하고 하드웨어 차원 에서 문 제 를 생각 하 는 데 전념 합 니 다.예 를 들 어 위조 공 유 를 어떻게 피 하 는 지 등'큰 소!
그것 의 블 로그 주 소 는 바로 위 에 있 는 이 주소 입 니 다.그 안에 밑바닥 지식 이 많 고 관심 이 있 으 면 가 볼 수 있 습 니 다.
(3)Dennis Byrne 의'Memory Barriers and JVM Concurrency'
https://www.infoq.com/articles/memory_barriers_jvm_concurrency
이것 은 InfoQ 영문 사이트 위의 글 입 니 다.저 는 잘 썼 다 고 생각 합 니 다.대체적으로 위의 두 가지 관점 을 종합 하고 어 셈 블 리 차원 에서 메모리 장벽 의 실현 을 분 석 했 습 니 다.
현재 국내 시장 에 서 는 메모리 장벽 에 대한 설명 이 이 세 편의 글 을 넘 지 않 고 관련 서적 에 대한 소 개 를 포함한다.
우 리 는 메모리 장벽 의 영향 을 이해 하기 위해 예 를 들 어 보 자.

public class VolatileTest4 {
 // a   volatile  
 public static long a = 0;
 //         
 public static long p1, p2, p3, p4, p5, p6, p7;
 // b  volatile  
 public static volatile long b = 0;
 //         
 public static long q1, q2, q3, q4, q5, q6, q7;
 // c   volatile  
 public static long c = 0;

 public static void main(String[] args) throws InterruptedException {
 new Thread(()->{
  while (a == 0) {
  long x = b;
  }
  System.out.println("a=" + a);
 }).start();

 new Thread(()->{
  while (c == 0) {
  long x = b;
  }
  System.out.println("c=" + c);
 }).start();

 Thread.sleep(100);

 a = 1;
 b = 1;
 c = 1;
 }
}
이 코드 에서 a 와 c 는 volatile 수식 을 사용 하지 않 고 b 는 volatile 수식 을 사용 합 니 다.또한 우 리 는 a/b,b/c 사이 에 각각 7 개의 long 필드 를 추가 하여 의사 공유 의 영향 을 제거 합 니 다.
위 공유 에 관 한 지식 은 동 형 이 전에 쓴 글[잡담 은 위 공유(false sharing)가 무엇 입 니까?]를 볼 수 있 습 니 다.
a 와 c 의 두 스 레 드 while 순환 에서 b 를 가 져 오 는 것 이 어 떻 습 니까?만약 long x=b 를이 줄 을 빼 면 요?운행 해 봐.
동 형 은 여기 서 직접적 으로 결론 을 내 렸 다.volatile 변수의 영향 범 위 는 자신 만 포함 하 는 것 이 아니 라 상하 변수 값 의 읽 기와 쓰기 에 모두 영향 을 줄 것 이다.
결함.
위 에서 우리 가 volatile 키워드 의 두 가지 의 미 를 소개 했다.그렇다면 volatile 키 워드 는 만능 이 아 닐 까?
물론 우리 메모리 모델 이 말 한 일치 성 이 포함 하 는 세 가지 특성 을 잊 었 습 니까?
일치 성 은 주로 세 가지 특성 을 포함한다.원자 성,가시 성,질서 성 이다.
volatile 키 워드 는 가시 성과 질서 성 을 보장 할 수 있 습 니 다.그러면 volatile 은 원자 성 을 보장 할 수 있 습 니까?
아래 의 예 를 보십시오.

public class VolatileTest5 {
 public static volatile int counter = 0;

 public static void increment() {
 counter++;
 }

 public static void main(String[] args) throws InterruptedException {
 CountDownLatch countDownLatch = new CountDownLatch(100);
 IntStream.range(0, 100).forEach(i->
  new Thread(()-> {
   IntStream.range(0, 1000).forEach(j->increment());
   countDownLatch.countDown();
  }).start());

 countDownLatch.await();

 System.out.println(counter);
 }
}
이 코드 에서 우 리 는 100 개의 스 레 드 를 시작 하여 각각 conter 를 1000 번 증가 시 켰 다.모두 100000 번 증가 해 야 하지만 실제 운행 결 과 는 영원히 100000 에 이 르 지 못 할 것 이다.
increment()방법의 바이트 코드 를 살 펴 보 겠 습 니 다.(IDEA 에서 관련 플러그 인 을 다운로드 하면 볼 수 있 습 니 다)
0 getstatic #2
3 iconst_1
4 iadd
5 putstatic #2
8 return
counter++가 네 개의 명령 으로 분 해 된 것 을 볼 수 있 습 니 다.
(1)getstatic,conter 현재 값 을 가 져 와 스 택 에 병합 합 니 다.
(2)iconst_1,스 택 int 형식의 값 1
(3)iadd,창고 꼭대기 의 두 값 을 더 합 니 다.
(4)putstatic,추 가 된 결 과 를 counter 에 다시 씁 니 다.
counter 는 volatile 로 장식 되 어 있 기 때문에 getstatic 는 메 인 메모리 에서 최신 값 을 새로 고 칩 니 다.putstatic 도 수 정 된 값 을 메 인 메모리 로 즉시 동기 화 합 니 다.
하지만 가운데 두 걸음 iconst1.아 이 디 와 실행 하 는 과정 에서 counter 의 값 이 수정 되 었 을 수 있 습 니 다.이 때 메 인 메모리 의 최신 값 을 다시 읽 지 않 았 기 때문에 volatile 은 counter+이 장면 에서 원자 성 을 보장 할 수 없습니다.
volatile 키 워드 는 가시 성과 질서 성 만 보장 할 수 있 고 원자 성 을 보장 할 수 없 으 며 원자 성 문 제 를 해결 하려 면 자 물 쇠 를 추가 하거나 원자 류 를 사용 하 는 방식 으로 만 해결 해 야 합 니 다.
더 나 아가 우 리 는 volatile 키워드 가 사용 하 는 장면 을 얻 었 다.
(1)연산 결 과 는 변수의 현재 값 에 의존 하지 않 거나 단일 한 스 레 드 만 변 수 를 수정 하 는 값 을 확보 할 수 있 습 니 다.
(2)변 수 는 다른 상태 변수 와 함께 변 하지 않 는 제약 에 참여 할 필요 가 없다.
말하자면 volatile 자체 가 원자 성 을 보장 하지 않 는 다 는 것 이다.그러면 다른 제약 조건 을 증가 시 켜 서 그 가 있 는 장면 자체 가 원자 가 되도록 해 야 한다.
예 를 들 면:

private volatile int a = 0;

//   A
a = 1;

//   B
if (a == 1) {
 // do sth
}
a = 1;이 할당 작업 자체 가 원자 이기 때문에 volatile 로 수식 할 수 있 습 니 다.
총결산
(1)volatile 키 워드 는 가시 성 을 확보 할 수 있다.
(2)volatile 키 워드 는 질서 성 을 확보 할 수 있다.
(3)volatile 키 워드 는 원자 성 을 보장 할 수 없다.
(4)volatile 키워드 의 밑바닥 은 주로 메모리 장벽 을 통 해 이 루어 진다.
(5)volatile 키워드 의 사용 장면 은 반드시 장면 자체 가 원자 여야 한다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기