자바 의 다 중 스 레 드 와 병발volatile 키워드 의 깊 은 이해

1.volatile 키워드
volatile 은 JVM 이 제공 하 는 경량급 동기 화 메커니즘 으로 특성:
1.메모리 의 가시 성 확보
2.원자 성 을 보장 하지 않 는 다
3.명령 어 정렬 방지
2.JMM(Java Memory Model)
자바 메모리 모델 은 모든 변 수 를 주 메모리 에 저장 하도록 규정 하고 있 습 니 다(예 를 들 어 가상 컴퓨터 물리 메모리 의 일부분).모든 스 레 드 는 자신의 작업 메모리(예 를 들 어 CPU 의 고속 캐 시)가 있 습 니 다.스 레 드 의 작업 메모리 에는 이 스 레 드 가 사용 하 는 변 수 를 주 메모리 의 복사 본 에 저장 하고 스 레 드 는 변 수 를 모든 작업(읽 기,읽 기,할당)주 메모리 의 변 수 를 직접 읽 지 않 고 작업 메모리 에서 진행 해 야 합 니 다.서로 다른 스 레 드 간 에 상대방 의 작업 메모리 에 있 는 변 수 를 직접 방문 할 수 없습니다.스 레 드 간 변수 값 의 전달 은 모두 메 인 메모리 로 이 루어 져 야 합 니 다.스 레 드,메 인 메모리 와 작업 메모리 의 상호작용 관 계 는 다음 그림 과 같 습 니 다.

검증
1.volatile 의 가시 성 검증
1.1 만약 int num=0;num 변 수 는 이전에 volatile 키워드 수식 을 추가 하지 않 았 고 보이 지 않 았 습 니 다.
1.2 volatile 을 추가 하여 가시 적 인 문 제 를 해결 할 수 있 습 니 다.
MyData 클래스

class MyData {
 volatile int num = 0;

 public void addT060() {
 this.num = 60;
 }
}
메모리 가시 성 검증,그 중 두 스 레 드 는 각각 AAA 스 레 드 와 main 스 레 드 입 니 다.

 //volatile       ,        ,          
 @Test
 public void seeOkByVolatile() {
 MyData myData = new MyData();//   

 new Thread(() -> {
  System.out.println(Thread.currentThread().getName() + "\t come in");
  //      
  try{
  TimeUnit.SECONDS.sleep(3);
  }catch (InterruptedException e) {
  e.printStackTrace();
  }
  myData.addT060();
  System.out.println(Thread.currentThread().getName() + "\t update num value: " + myData.num);
 },"AAA").start();

 // 2       main  
 while (myData.num == 0) {
  //main            ,  num     0.
 }
 System.out.println(Thread.currentThread().getName() + "\t mission is over,main get num value: " + myData.num );
 }
num 변수 에 volatile 수식 후 결과
AAA come in
AAA update num value: 60
main AAA 스 레 드 가 num 에 대한 수정 결 과 를 볼 수 있 습 니 다.main get num value:60
Process finished with exit code 0
2.volatile 이 원자 성 을 보장 하지 않 는 지 검증
2.1 원자 성 이란 무슨 뜻 입 니까?
분할 할 수 없 는 완전 성,즉 특정한 스 레 드 가 특정한 임 무 를 하고 있 을 때 중간 에 끼어 들 거나 분할 되 어 서 는 안 된다.전체 가 온전 해 야 한다.동시에 성공 하거나 동시에 실패 하 다.
2.2 volatile 원자 성 을 보장 하지 않 는 사례 시연
2.3 왜 원자 성 을 보장 하지 않 습 니까?
2.4 원자 성 을 어떻게 보장 합 니까
동기 화 추가
저희 jc 하의 AtomicInteger 를 사용 하 세 요.
MyData 클래스 에 addPlus()방법 추가

class MyData {//MyData.java ===> MyData.class ===> JVM   
 int num = 0;

 public void addT060() {
 this.num = 60;
 }

 //   ,  num           ,volatile      
 public void addPlusPlus() {
 num++;
 }
}
2.2 volatile 원자 성 을 보장 하지 않 는 사례 시연
num+다 중 스 레 드 작업 의 경우 원자 성 을 보장 하지 않 습 니 다.
20 개의 스 레 드 를 만 들 고 num+작업 2000 회 를 병행 합 니 다.여러 번 테스트 한 결과 40000 이 아 닙 니 다.

public static void main(String[] args) {
 MyData myData = new MyData();

 for (int i = 1; i <= 20; i++ ) {

  new Thread(() -> {
  for (int j = 1; j <= 2000; j++) {
   myData.addPlusPlus();
  }

  },String.valueOf(i)).start();
 }

 //      20           ,  main              ?
 while(Thread.activeCount() > 2) {
  Thread.yield();
 }

 System.out.println(Thread.currentThread().getName() + "\t finally num value:" + myData.num);
 }
결과:수치 가 40000 보다 적 으 면 쓰기 값 을 잃 어 버 리 는 경우 가 발생 합 니 다.
main  finally num value:38480
Process finished with exit code 0
2.3 왜 원자 성 을 보장 하지 않 습 니까?
스 레 드 A 가 num+작업 을 자신의 작업 메모리 에서 메 인 메모리 로 갱신 할 때 다른 스 레 드 메 인 메모리 변수 가 업데이트 되 는 순간 까지 알 리 지 않 았 기 때문에 다른 스 레 드 가 num 변 수 를 조작 한 결과 도 메 인 메모리 에 새로 고침 을 하여 쓰기 값 이 손실 되 었 습 니 다.
num+어 셈 블 리 명령 분석 을 통 해 자바 p 역 컴 파일 을 통 해 다음 과 같은 어 셈 블 리 명령 을 얻 을 수 있 습 니 다.

class com.slx.juc.MyData {
 volatile int num;

 com.slx.juc.MyData();
 Code:
 0: aload_0
 1: invokespecial #1   // Method java/lang/Object."<init>":()V
 4: aload_0
 5: iconst_0
 6: putfield #2   // Field num:I
 9: return

 public void addT060();
 Code:
 0: aload_0
 1: bipush 60
 3: putfield #2   // Field num:I
 6: return

 public void addPlusPlus();
 Code:
 0: aload_0
 1: dup
 2: getfield #2   // Field num:I
 5: iconst_1
 6: iadd
 7: putfield #2   // Field num:I
 10: return
}
이 를 통 해 알 수 있 듯 이 num++는 3 단계 로 나 뉘 어 져 있 으 며,약칭:읽 기-고치 기-쓰기
  • getfield 를 실행 하여 원본 num 가 져 오기;
  • iadd 를 실행 하여 1 조작 을 추가 합 니 다
  • 4.567917.putfield 를 실행 하여 누적 후의 값 을 다시 씁 니 다.
    2.4 원자 성 을 어떻게 보장 합 니까
    동기 화 추가
    저희 jc 하의 AtomicInteger 를 사용 하 세 요.
    MyData 클래스 에 원자 류 조작 방법 추가
    
     AtomicInteger atomicInteger = new AtomicInteger();
     public void addMyAtomic() {
     atomicInteger.getAndIncrement();
     }
    
    이 방법 으로 결 과 를 인쇄 합 니 다.
    
     public static void main(String[] args) {
     MyData myData = new MyData();
    
     for (int i = 1; i <= 20; i++ ) {
    
      new Thread(() -> {
      for (int j = 1; j <= 2000; j++) {
       myData.addMyAtomic();
      }
    
      },String.valueOf(i)).start();
     }
    
     //      20           ,  main              ?
     while(Thread.activeCount() > 2) {
      Thread.yield();
     }
    
     System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type ,finally num value:" + myData.atomicInteger);
     }
    
    테스트 결 과 는 40000 이 며,이전 int 형식의 손실 값 은 나타 나 지 않 습 니 다.
    main  AtomicInteger type ,finally num value:40000
    Process finished with exit code 0
    총결산
    자바 의 다 중 스 레 드 와 병발 에 관 한 이 편volatile 키워드 의 글 은 여기까지 입 니 다.자바 다 중 스 레 드 와 병발volatile 키워드 내용 은 저희 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 찾 아 보 세 요.앞으로 도 많은 응원 부 탁 드 리 겠 습 니 다!

    좋은 웹페이지 즐겨찾기