(1) JUC 의 volatile 키워드

4600 단어 jucvolatile가시 성
더 읽 기
 
최근 프로젝트 에 사용 되 는 병발 과 다 중 스 레 드 가 많 습 니 다. 그 전에 알 고 있 었 지만 완전한 학습 이 없 었 습 니 다. 이번 기회 에 자바 util. concurrent 가방 을 완전 하 게 정리 하여 배 웠 습 니 다.
jc 가방 을 연구 하려 면 먼저 volatile 키워드 와 cas 알고리즘 을 연구 해 야 한다. 왜냐하면 jc 가방 에서 많은 곳 에서 volatile 과 cas 를 사 용 했 기 때문에 오늘 은 volatile 을 먼저 쓴다.
volatile 키 워드 는 이전에 도 많 든 적 든 접촉 한 적 이 있 습 니 다. 그 전에 다 중 스 레 드 간 의 메모리 가시 성 문 제 를 해결 하기 위해 서 만 알 고 사용 할 뿐 그 원 리 를 추궁 하지 않 았 습 니 다.
만약 에 우리 가 이런 수요 가 있다 면 프로그램 은 두 개의 스 레 드 가 있 고 두 개의 스 레 드 는 하나의 스위치 파 라 메 터 를 공유 합 니 다. 스 레 드 A 가 스위치 를 닫 으 면 스 레 드 B 가 실 행 됩 니 다. 그렇지 않 으 면 스 레 드 B 가 계속 실 행 됩 니 다.
테스트 프로그램 작성:
public class VolatileTest {
    //  main      B
    public static void main(String args[]) {
        ThreadA threadA = new ThreadA();
        new Thread(threadA).start();

        while (true) {
            if(!threadA.isFlag()) {
                System.out.println("     ,  ...");
                break;
            }
        }
    }
}

class ThreadA implements Runnable {

    private boolean flag = true;

    @Override
    public void run() {
        try {
            Thread.sleep(2000); //    
        } catch (InterruptedException e) {
        }
        flag = false;
        System.out.println("  A   ...");
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

 
우리 의 이상 적 인 운행 결 과 는 다음 과 같다.
  A   ...
     ,  ... 

 
그러나 실제 운행 결 과 는 다음 과 같다.
  A   ...

 그리고 메 인 스 레 드 는 while 에서 계속 순환 합 니 다.
정상 적 인 사고방식 에 따 르 면 우 리 는 이미 라인 B 에 있다
(메 인 스 레 드)
순환 실행 과정 에서 스 레 드 A 를 통 해 flag 의 값 을 false 로 수 정 했 지만 스 레 드 B (Main 스 레 드) 는 계속 순환 하지만 종료 조건 에 이 르 지 못 했 습 니 다. 즉, 스 레 드 B 가 져 온 것 입 니 다.
threadA. isFlag () 의 값 은 항상 true 입 니 다. 이상 하지 않 습 니까? 그런데 왜 그런 지 그 이 유 를 살 펴 보 겠 습 니 다.
프로그램 이 실 행 될 때 모든 명령 은 CPU 에서 실 행 됩 니 다. 데 이 터 를 읽 고 기록 하 는 것 을 포함 합 니 다. 프로그램 이 실 행 될 때 임시 데 이 터 는 메 인 메모리 (물리 적 메모리) 에 저장 되 어 있 기 때문에 문제 가 있 습 니 다. CPU 의 실행 속도 가 매우 빠 르 기 때 문 입 니 다.메모리 에서 데 이 터 를 읽 고 메모리 에 데 이 터 를 기록 하 는 과정 은 CPU 가 명령 을 수행 하 는 속도 보다 훨씬 느 리 기 때문에 언제든지 데이터 에 대한 조작 이 메모리 와 의 상호작용 을 통 해 이 루어 지면 명령 의 실행 속 도 를 크게 낮 출 수 있다.그래서 CPU 에 고속 캐 시가 생 겼 다.즉, 프로그램 이 실행 되 는 과정 에서 연산 에 필요 한 데 이 터 를 메 인 메모리 에서 CPU 의 캐 시 로 복사 하면 CPU 가 계산 할 때 직접 캐 시 에서 데 이 터 를 읽 고 데 이 터 를 기록 할 수 있 으 며, 연산 이 끝 난 후에 캐 시 에 있 는 데 이 터 를 메 인 메모리 로 새로 고 칠 수 있다.
이상 과 결합 하여 프로그램 을 살 펴 보 겠 습 니 다. 먼저 메 인 메모리 에 flag = true 를 설 명 했 습 니 다. 이때 스 레 드 B (Main 스 레 드) 는 메 인 메모리 에서 flag = true 의 값 을 가 져 와 캐 시 에 넣 었 습 니 다. 스 레 드 A 는 메 인 메모리 에서 flag = true 를 가 져 와 캐 시 에 넣 었 습 니 다. 이 어 flag = flase 를 수정 하여 메 인 메모리 의 값 을 업데이트 합 니 다. flag = false, 그러나 스 레 드 B 에 flag = true 가 캐 시 되 어 있 습 니 다. 메 인 메모리 의 값 을 동기 화하 지 않 았 기 때문에 우리 가 자주 말 하 는 메모리 의 가시 적 인 문 제 를 일 으 켰 습 니 다.
그러면 우 리 는 이전에 알 게 된 지식 을 결합 하여 이 문 제 를 해결 합 시다.
while (true) {
            synchronized (threadA) {
                if(!threadA.isFlag()) {
                    System.out.println("     ,  ...");
                    break;
                }
            }
        }
  주 스 레 드 를 실행 할 때 스 레 드 A 에 synchronized 를 추가 합 니 다. 이 때 실행 결 과 는 기대 치 와 일치 합 니 다 (Lock 도 가능 합 니 다).
  A   ...
     ,  ... 
 그러나 synchronized 를 아 는 사람 은 모두 알 고 있 을 것 입 니 다. synchronized 는 상호 배척 자물쇠 입 니 다. 즉, 스 레 드 B 는 스 레 드 A 가 실 행 될 때 까지 기 다 려 야 계속 실행 할 수 있 습 니 다. 메모리 의 가시 적 인 문 제 를 해결 하기 위해 자바 는 volatile 키 워드 를 제공 하여 가시 성 을 확보 합 니 다. 우 리 는 절 차 를 수정 하고 실행 결 과 를 다시 보 겠 습 니 다.
private volatile boolean flag = true;
 falg 를 volatile 변수 로 설명 하고 이전 단계 에 추 가 된 동기 화 잠 금 (synchronized) 을 삭제 합 니 다. 실행 결 과 는 다음 과 같 습 니 다.
  A   ...
     ,  ... 
 그런데 왜 변 수 를 volatile 로 수식 하면 메모리 의 가시 성 을 확보 할 수 있 습 니까? 주로 다음 과 같은 몇 가지 가 있 습 니 다.
첫째: volatile 키 워드 를 사용 하면 수 정 된 값 을 메 인 메모리 에 즉시 기록 하도록 강제 합 니 다.
둘째: volatile 키 워드 를 사용 하면 스 레 드 A 를 수정 할 때 스 레 드 B 의 작업 메모리 에 캐 시 변수 flag 의 캐 시 줄 이 잘못 되 었 습 니 다 (하드웨어 계층 에 반영 되면 CPU 의 L1 또는 L2 캐 시 에 대응 하 는 캐 시 줄 이 잘못 되 었 습 니 다).
셋째: 스 레 드 B 의 작업 메모리 에 캐 시 변수 flag 의 캐 시 줄 이 잘못 되 었 기 때문에 스 레 드 B 가 변수 flag 의 값 을 다시 읽 을 때 메 인 저장 소 에서 읽 습 니 다.
그럼 volatile 은 synchronized 를 대체 할 수 있 습 니까? 다음 에 이 문 제 를 다시 쓰 겠 습 니 다.
 

좋은 웹페이지 즐겨찾기