자바 의 다 중 스 레 드 높 은 병행 상세 설명

1.JMM 데이터 원자 조작
  • read(읽 기)∶메 인 메모리 에서 데 이 터 를 읽 습 니 다
  • load(불 러 오기):주 메모리 에서 읽 은 데 이 터 를 작업 메모리 에 기록 합 니 다
  • use(사용):작업 메모리 에서 데 이 터 를 읽 어 계산 합 니 다
  • assign(할당):계 산 된 값 을 작업 메모리 에 다시 할당 합 니 다
  • store(저장):작업 메모리 데 이 터 를 주 메모리 에 기록 합 니 다
  • write(쓰기):store 의 과거 변수 값 을 주 메모리 의 변수 에 할당 합 니 다
  • lock(잠 금):메 인 메모리 변 수 를 잠 그 고 표 지 는 스 레 드 독점 상태 입 니 다
  • unlock(잠 금 해제):메 인 메모리 변 수 를 잠 금 해제 하고 잠 금 해제 후 다른 스 레 드 는 이 변 수 를 잠 글 수 있 습 니 다
  • 2.volatile 키워드 살 펴 보기
    (1)두 스 레 드 시작
    
    public class VolatileDemo {
     
        private static boolean flag = false;
        public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                while (!flag){
                }
                System.out.println("  while   ");
            }).start();
     
            Thread.sleep(2000);
            new Thread(() -> changeFlage()).start();
        }
     
        private static void changeFlage() {
            System.out.println("    flag   ");
            flag = true;
            System.out.println("  flag   ");
        }
    }
    
    volatile 을 추가 하기 전에 첫 번 째 스 레 드 의 while 판단 은 만족 했다.

    (2)변수 flag 에 volatile 를 추가 한 후
    
    public class VolatileDemo {
     
        private static volatile boolean flag = false;
        public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                while (!flag){
                }
                System.out.println("  while   ");
            }).start();
     
            Thread.sleep(2000);
            new Thread(() -> changeFlage()).start();
        }
     
        private static void changeFlage() {
            System.out.println("    flag   ");
            flag = true;
            System.out.println("  flag   ");
        }
    }
    
    while 문 구 는 조건 을 만족 시 킬 수 있다.

    (3)원리 해석:
    첫 번 째 스 레 드 를 열 때 flag 변 수 는 read 를 통 해 메 인 메모리 에서 데 이 터 를 읽 고 load 를 사용 하여 스 레 드 1 의 작업 메모리 에 데 이 터 를 불 러 옵 니 다.use 를 통 해 flag 를 스 레 드 로 읽 습 니 다.스 레 드 2 도 같은 읽 기 동작 입 니 다.스 레 드 2 는 assign 을 통 해 flag 의 값 을 바 꾸 었 습 니 다.스 레 드 2 작업 메모리 에 저 장 된 flag=true 는 store 를 통 해 flag 를 버스 에 기록 하고 버스 는 flag 를 write 를 통 해 메모리 에 기록 합 니 다.두 스 레 드 읽 기 동작 은 모두 각종 작업 메모리 의 값 이 고 메 인 메모리 의 복사 본 이 며 서로 통신 하지 않 기 때문에 스 레 드 가 계속 순환 되 고 스 레 드 1 의 flag 는 false 입 니 다.
    volatile 을 추가 한 후 캐 시 일치 프로 토 콜(MESI)을 추 가 했 습 니 다.CPU 는 버스 탐지 기 를 통 해 데이터 의 변 화 를 감지 하고 자신의 캐 시 에 있 는 값 이 효력 을 잃 었 습 니 다.이 때 스 레 드 는 작업 메모리 에 저 장 된 flag 를 효력 을 잃 고 메 인 메모리 에서 flag 의 값 을 다시 읽 습 니 다.이 때 는 while 조건 이 가득 합 니 다.
    volatile 바 텀 은 어 셈 블 리 언어의 lock 수식 을 통 해 변 수 를 수정 하면 바로 주 류 를 쓰 고 명령 을 다시 정렬 하지 않도록 합 니 다.

    3.동시 프로 그래 밍 3 대 특성
    가시 성,질서 성,원자 성
    4.더 블 잠 금 판단 메커니즘 생 성 단일 모드
    
    public class DoubleCheckLockSinglenon {
     
        private static volatile DoubleCheckLockSinglenon doubleCheckLockSingleon = null;
     
        public DoubleCheckLockSinglenon(){}
     
     
     
        public static DoubleCheckLockSinglenon getInstance(){
            if (null == doubleCheckLockSingleon) {
                synchronized(DoubleCheckLockSinglenon.class){
                    if(null == doubleCheckLockSingleon){
                        doubleCheckLockSingleon = new DoubleCheckLockSinglenon();
                    }
                }
            }
            return doubleCheckLockSingleon;
        }
     
     
        public static void main(String[] args) {
            System.out.println(DoubleCheckLockSinglenon.getInstance());
     
        }
     
    }
    
    스 레 드 가 getInstance 방법 을 호출 하여 만 들 때 빈 것 인지 아 닌 지 를 판단 하고 빈 것 은 대상 에 자 물 쇠 를 추가 합 니 다.그렇지 않 으 면 다 중 스 레 드 의 경우 중복 을 만 들 고 자물쇠 안에 빈 것 인지 다시 판단 합 니 다.new 대상 일 때 메모리 에 공간 을 분배 한 다음 이미지 의 init 속성 부 영 작업 을 실행 한 다음 에 초기 화 할당 작업 을 수행 합 니 다.
    cpu 는 코드 실행 효율 을 최적화 하기 위해 as-if-serial 과 happens-before 원칙 을 만족 시 키 는 코드 에 대해 명령 재 정렬 을 하고 as-if-serial 은 스 레 드 내의 실행 코드 순 서 를 결과 출력 에 영향 을 주지 않 으 면 명령 재 정렬 을 한다.
    happens-before 는 잠 금 순 서 를 정 하고 같은 대상 의 unlock 은 다음 lock 이 나타 나 기 전에 등 을 정 해 야 합 니 다.
    따라서 new 를 방지 하기 위해 서 는 명령 이 다시 배열 되 는 것 을 방지 하기 위해 서 는 먼저 할당 을 한 다음 에 부 영 작업 을 수행 해 야 합 니 다.volatile 장식 부 호 를 추가 하고 volatile 장식 부 호 를 추가 한 후에 new 작업 을 할 때 메모리 장벽 을 만 듭 니 다.고속 cpu 는 명령 을 다시 정렬 하지 않 고 바 텀 은 lock 키워드 입 니 다.메모리 장벽 은 LoadLoad(읽 기),storestore(쓰기),loadstore(읽 기 쓰기),storeload(쓰기)로 나 뉘 는데,밑바닥 은 c++코드 로 쓰 이 고,c+코드 는 어 셈 블 리 언어 로 호출 된다.
    5.synchronized 키워드
    (1)synchronized 를 추가 하기 전에
    
    package com.qingyun;
     
    /**
     * Synchronized   
     */
    public class SynchronizedDemo {
     
        public static void main(String[] args) throws InterruptedException {
     
            Num num = new Num();
           Thread t1 = new Thread(() -> {
                for (int i = 0;i < 100000;i++) {
                    num.incrent();
                }
            });
            t1.start();
     
            for (int i = 0;i < 100000;i++) {
                num.incrent();
            }
            t1.join();
            System.out.println(num.getNum());
        }
    }
    
    
    package com.qingyun;
     
    public class Num {
     
        public int num = 0;
     
        public void incrent() {
            num++;
        }
     
        public int getNum(){
            return num;
        }
    }
    
    출력 결 과 는 우리 가 원 하 는 것 이 아 닙 니 다.스 레 드 와 for 순환 을 동시에 추가 하 는 방법 으로 인해 마지막 출력 결 과 는 우리 가 원 하 는 것 이 아 닙 니 다.

    (2)synchronized 를 더 한 후
    
    public synchronized void incrent() {
            num++;
        }
     
    //  
     
      public  void incrent() {
            synchronized(this){
                num++;
            }
        }
    

    출력 결 과 는 우리 가 원 하 는 것 입 니 다.synchronized 키워드 밑 에 사용 되 는 lock 은 중량급 자물쇠 입 니 다.서로 배척 하고 비관 적 인 자물쇠 입 니 다.jdk 1.6 이전의 자물쇠 입 니 다.스 레 드 는 한 대기 열 에 넣 어 실행 을 기다 리 고 있 습 니 다.
    6.AtomicIntger 원자 조작
    (1)원자 에 1 을 더 하 는 조작 은 AtomicInteger 를 사용 하여 실현 할 수 있 으 며,synchronized 에 비해 성능 이 크게 향상 되 었 다.
    
    public class Num {
     
       // public int num = 0;
        AtomicInteger atomicInteger = new AtomicInteger();
     
        public  void incrent() {
           atomicInteger.incrementAndGet(); //   1
        }
     
        public int getNum(){
            return atomicInteger.get();
        }
    }
    
    AtomicInteger 소스 코드 는 value 필드 가 있 습 니 다.volatile 수식 을 사용 하고 volatile 바 텀 은 lock 수식 을 사용 하여 다 중 스 레 드 병행 결과 의 정확 함 을 확보 합 니 다.
    
    private volatile int value;
    (2)atomicInteger.increment AndGet()방법 으로 하 는 일:value 의 값 을 먼저 얻 고 값 에 1 을 더 한 다음 에 낡은 값 과 atomicInteger 를 비교 하여 new Value 를 설정 하 는 것 과 같 습 니 다.다 중 스 레 드 를 사용 하면 값 이 같 지 않 을 수 있 기 때문에 while 를 사용 하여 순환 비 교 를 하고 실행 이 끝 난 후에 출시 합 니 다.
    
    while(true) {
        int oldValue = atomicInteger.get();
        int newValue = oldValue+1;
        if(atomicInteger.compareAndSet(oldValue,newValue)){
           break;
        }
    }
    
    (3)atomicInteger.com pareAndSet 비 교 를 마 친 후에 야 새로운 값 을 설정 하 는 방식 은 바로 CAS 입 니 다.잠 금 없 음,낙관적 잠 금,경량급 잠 금,synchroznied 에 스 레 드 블록 이 존재 하고 상행 문 전환,운영 체제 스케줄 링 이 비교적 걸 립 니 다.CAS 는 실행 보다 계속 순환 하여 효율 이 높다.
    (4)copare AndSetInt 바 텀 은 native 수식 을 사용 하고 바 텀 은 c+코드 로 원자 문 제 를 실현 하 며 어 셈 블 리 언어 에서 코드 lock cmpxchqq 를 사용 하여 원자 성 을 확보 하 며 캐 시 줄 잠 금 입 니 다.
    (5)ABA 문제:스 레 드 1 부터 하나의 변수 까지 스 레 드 2 의 실행 이 비교적 빠 르 고 이 변 수 를 가 져 와 변수의 값 을 수정 한 다음 에 원래 의 값 으로 신속하게 수정 합 니 다.이런 변수의 값 은 한 번 의 변화 가 있 었 습 니 다.스 레 드 가 compare Andset 를 다시 실 행 했 을 때 값 은 예전 의 것 이 아니 지만 이미 변화 가 생 겼 습 니 다.ABA 문제 가 발생 했 습 니 다.
    (6)ABA 문 제 를 해결 하 는 것 은 변수 에 버 전 을 추가 하 는 것 입 니 다.매번 조작 변수 버 전에 1 을 추가 하 는 것 입 니 다.JDK 테이프 버 전의 잠 금 은 Atomic Stamped Reference 가 있 습 니 다.그러면 변수 가 다른 스 레 드 에 의 해 수정 되 었 더 라 도 버 전 번호 가 일치 하지 않 습 니 다.
    7.자물쇠 최적화
    (1)헤비급 자 물 쇠 는 기다 리 는 스 레 드 를 대기 열 에 두 고 헤비급 자 물 쇠 는 Monitor 로 잠 겨 있 으 며 상하 문 전환 자원 의 점용 이 존재 합 니 다.경량급 자물쇠 가 스 레 드 가 너무 많 으 면 자전 이 존재 하고 cpu 를 소모 합 니 다.
    (2)jdk 1.6 이후 잠 금 이 무 상태-편향 잠 금(잠 금 id 지정)-경량급 잠 금(자전 팽창)-중량급 잠 금(대기 열 저장)으로 업그레이드 되 었 습 니 다.
    (3)대상 을 만 듭 니 다.이 때 대상 은 무 상태 입 니 다.스 레 드 를 시작 할 때 대상 을 만 들 때 편향 자 물 쇠 를 사용 합 니 다.편향 자 물 쇠 를 실행 한 후에 자 물 쇠 를 풀 지 않 습 니 다.한 스 레 드 를 다시 사용 할 때 두 개의 스 레 드 가 강탈 대상 이 있 을 때 바로 자 물 쇠 를 경량급 자물쇠 로 업그레이드 하 는 경향 이 있다.스 레 드 를 하나 더 만들어 서 대상 자 물 쇠 를 뺏 을 때 경량급 자물쇠 에서 헤비급 자물쇠 로 업그레이드 합 니 다.
    (4)세그먼트 CAS,바 텀 에 base 기록 변수 값 이 있 습 니 다.여러 스 레 드 류 가 이 변 수 를 방문 하면 base 의 값 은 여러 cell 로 나 뉘 어 배열 을 구성 합 니 다.각 cell 은 하나 에서 여러 스 레 드 의 ca 처리 에 대응 하여 스 레 드 의 자전 공전 을 피 할 수 있 습 니 다.이렇게 하면 경량급 자물쇠 입 니 다.데 이 터 를 되 돌 릴 때 바 텀 은 모든 cell 배열 과 base 의 더하기 입 니 다.
    
    public class Num {
     
        LongAdder longAdder = new LongAdder();
     
        public  void incrent() {
     
            longAdder.increment();
        }
     
        public long getNum(){
           return longAdder.longValue();
        }
    }
    
    
    public long longValue() {
            return sum();
        }
    
    public long sum() {
            Cell[] as = cells; Cell a;
            long sum = base;
            if (as != null) {
                for (int i = 0; i < as.length; ++i) {
                    if ((a = as[i]) != null)
                        sum += a.value;
                }
            }
            return sum;
        }
    자바 의 다 중 스 레 드 와 높 은 동시 다발 에 관 한 상세 한 글 은 여기까지 소개 되 었 습 니 다.더 많은 자바 다 중 스 레 드 와 높 은 동시 다발 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 바 랍 니 다!

    좋은 웹페이지 즐겨찾기