독서 노트 (4): 자물쇠 의 최적화 와 주의사항

12936 단어 독서 노트
제4 장 자물쇠 의 최적화 및 주의사항
4.1 잠 금 성능 향상 에 도움 이 되 는 몇 가지 제안
1. 자물쇠 보유 시간 감소
필요 할 때 만 동기 화 하 는 것 이다.
2. 자물쇠 입도 감소
예 를 들 어 Concurrent HashMap 에서 전체 HashMap 에 자 물 쇠 를 추가 하 는 것 이 아니 라 세그먼트 별로 자 물 쇠 를 추가 합 니 다.
주: JDK 1.8 이후 Concurrent HashMap 은 Segment 세그먼트 잠 금 을 취소 하고 CAS 와 synchronized 를 사용 하여 병행 안전 을 확보 합 니 다.데이터 구 조 는 HashMap 1.8 의 구조 와 유사 하 며, 배열 + 링크 / 레 드 블랙 이 진 트 리 입 니 다.synchronized 는 현재 링크 나 빨간색 과 검은색 두 갈래 나무의 첫 번 째 노드 만 잠 그 면 hash 가 충돌 하지 않 으 면 동시 다발 이 발생 하지 않 고 효율 이 N 배 높 아 집 니 다.
3. 읽 기와 쓰기 분리 자물쇠 로 독점 자 물 쇠 를 교체 합 니 다.
읽 기 동작 과 읽 기 동작 을 병행 할 수 있 으 며, 읽 기 쓰기 동작 만 잠 금 을 추가 해 야 합 니 다.
4. 자물쇠 분리
읽 기와 쓰기 자물쇠 의 사상 을 한층 더 연장 시 키 는 것 은 바로 자물쇠 분리 이다.예 를 들 어 링크 드 BlockingQueue 에서 take () 와 put () 함 수 는 각각 대기 열 에서 데 이 터 를 추출 하고 대기 열 에 데 이 터 를 추가 하 는 기능 을 실현 합 니 다.두 함수 가 모두 대기 열 을 수 정 했 지만 링크 드 BlockingQueue 는 링크 를 기반 으로 합 니 다. 두 동작 은 각각 대기 열의 첫 번 째 끝 과 끝 에 있 습 니 다. 두 동작 은 충돌 하지 않 습 니 다.따라서 JDK 의 실현 에서 두 개의 서로 다른 자물쇠 로 이 두 함 수 를 분리 한 것 은 이들 이 진정한 의미 에서 병발 한 것 이다.
5. 자물쇠 굵기
일반적인 상황 에서 모든 스 레 드 가 공공 자원 을 사용 한 후에 즉시 자 물 쇠 를 풀 어야 한다.그러나 같은 자물쇠 에 대해 끊임없이 요청, 동기 화, 방출 을 하면 시스템 자원 이 소모 된다.따라서 가상 머 신 은 같은 자 물 쇠 를 계속 요청 하고 풀 어 주 는 작업 을 할 때 모든 자 물 쇠 를 한 번 의 요청 으로 통합 시 켜 자물쇠 에 대한 요청 동기 화 횟수 를 줄 이 는 것 을 자물쇠 의 조화 라 고 한다.
4.2 자바 가상 컴퓨터 가 잠 금 최적화 에 대한 노력
4.2.1 자물쇠 편향
핵심 사상 은 한 스 레 드 가 자 물 쇠 를 얻 으 면 자 물 쇠 는 편향 모드 에 들 어가 고 이 스 레 드 가 다시 자 물 쇠 를 요청 할 때 동기 화 작업 을 하지 않 아 도 된다 는 것 이다.자물쇠 경쟁 이 거의 없 는 경우 에 편향 자물쇠 의 최적화 효과 가 비교적 좋다.경쟁 이 치열 한 성 화 는 그 효과 가 좋 지 않다.
4.2.2 경량급 자물쇠
편향 잠 금 에 실패 하면 가상 컴퓨터 는 스 레 드 를 즉시 걸 지 않 고 경량급 잠 금 이 라 고 불 리 는 최적화 수단 을 사용한다.경량급 자 물 쇠 는 대상 의 머리 를 지침 으로 하여 자 물 쇠 를 가 진 스 레 드 스 택 의 내 부 를 가리 키 며 하나의 스 레 드 가 대상 자 물 쇠 를 가지 고 있 는 지 여 부 를 판단 할 뿐이다.스 레 드 가 경량급 잠 금 에 성공 하면 임계 구역 에 순조롭게 들 어 갈 수 있다.경량급 자물쇠 추가 에 실패 하면 다른 스 레 드 가 먼저 자 물 쇠 를 쟁탈 한 것 을 나타 내 고 현재 스 레 드 의 자물쇠 요청 은 중량급 자물쇠 로 팽창 합 니 다.
4.2.3 자전거 자물쇠
자물쇠 가 팽창 한 후에 라인 이 운영 체제 차원 에서 걸 리 는 것 을 피하 기 위해 가상 기 회 는 자선 자 물 쇠 를 사용한다.시스템 은 머지않아 라인 이 이 자 물 쇠 를 얻 을 수 있 을 것 이 라 고 가정한다.따라서 가상 기 회 는 현재 스 레 드 로 하여 금 몇 개의 빈 순환 (이것 도 자전 의 의미) 을 하 게 하고 몇 번 의 순환 을 거 친 후에 자 물 쇠 를 얻 을 수 있 으 면 임계 구역 에 들 어가 야 걸 릴 수 있다.
4.2.4 자물쇠 제거
자 물 쇠 를 없 애 는 것 은 더욱 철저한 자물쇠 최적화 이다.자바 가상 머 신 은 JIT 컴 파일 시 컨 텍스트 스 캔 을 통 해 공유 자원 경쟁 이 불가능 한 자 물 쇠 를 제거 합 니 다.예 를 들 어 동시 경쟁 이 존재 하지 않 는 장소 에서 Vector 를 사용 했다.
4.3 펜 한 자루: ThreadLocal
자원 의 접근 을 통제 하 는 것 외 에 우 리 는 자원 을 늘 려 모든 대상 의 스 레 드 안전 을 확보 할 수 있다.예 를 들 어 100 명 에 게 개인 정보 표를 작성 하 게 하고 펜 한 자루 만 있 으 면 모두 가 하나씩 작성 해 야 한다.펜 100 자루, 한 자루 씩 준비 하면 금방 완성 할 수 있 습 니 다.
1. ThreadLocal 의 간단 한 사용
ThreadLocal 은 현재 스 레 드 만 접근 할 수 있 는 스 레 드 의 부분 변수 입 니 다.현재 스 레 드 만 접근 할 수 있 으 니 당연히 스 레 드 가 안전 합 니 다.
public class ThreadLocalDemo {

    static ThreadLocal t1 = new ThreadLocal<>();
//    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-ddHH:mm:ss");

    public static class parseDate implements Runnable {

        int i=0;

        public parseDate(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            try {
                if (t1.get() == null) {
                    t1.set(new SimpleDateFormat("yyyy-MM-ddHH:mm:ss"));
                }
                Date date = t1.get().parse("2019-04-01 19:29:" + i % 60);
                System.out.println(i+":"+date);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            es.execute(new parseDate(i));
        }
    }
}

ThreadLocal 을 사용 하 는 전 제 는 응용 차원 에서 모든 스 레 드 에 서로 다른 대상 을 분배 하 는 것 입 니 다. 만약 에 응용 에서 모든 스 레 드 에 같은 대상 인 스 턴 스 를 분배 하면 ThreadLocal 도 스 레 드 안전 을 보장 할 수 없습니다.
4.4 자물쇠 없 음
병발 제어 에 있어 자 물 쇠 는 비관 적 인 전략 으로 매번 의 임계 구역 작업 이 충돌 할 것 이 라 고 가정 한다.잠 금 이 없 는 것 은 낙관적 인 전략 으로 자원 에 대한 방문 이 충돌 하지 않 는 다 고 가정 할 것 이다.잠 금 정책 은 비교 교환 (CAS, Compare And Swap) 이라는 기술 을 사용 하여 스 레 드 충돌 을 감별 합 니 다.
1. 남 다른 병행 전략: 비교 교환
자물쇠 에 비해 비교 교환 은 프로그램 을 복잡 하 게 보이 지만 자물쇠 문제 에 대한 천연 면역 이 고 스 레 드 간 의 상호 영향 도 적다.더 중요 한 것 은 잠 금 전략 이 잠 금 경쟁 과 스 레 드 간 스케줄 링 이 가 져 온 시스템 비용 이 없고 성능 이 더욱 우수 하 다 는 것 이다.
CAS 알고리즘 의 과정: 세 개의 매개 변수 CAS 를 포함 합 니 다.(V, E, N), 이 중 V 는 업데이트 할 변 수 를, E 는 예상 값 을, N 은 새 값 을 나 타 냅 니 다. 업데이트 할 변 수 는 V 가 예상 값 E 와 같 을 때 만 V 를 새 값 N 으로 설정 합 니 다. V 와 E 가 다 르 면 현재 스 레 드 는 아무것도 하지 않 습 니 다. 마지막 으로 CAS 는 현재 V 의 실제 값 을 되 돌려 줍 니 다. 여러 스 레 드 가 동시에 CAS 를 사용 할 때 변 수 를 조작 합 니 다.이 길 수 있 고 변 수 를 성공 적 으로 업데이트 할 수 있 습 니 다. 나머지 는 실패 합 니 다. 실패 한 진행 은 끊 기지 않 습 니 다. 실패 만 알 리 고 다시 시도 할 수 있 습 니 다.
2. 잠 금 없 는 스 레 드 안전 정수: AtomicInteger
JDK 와 가방 에 atomic 가방 이 있 는데 그 안에 CAS 를 직접 사용 하 는 라인 이 안전 한 유형 을 실현 했다.
그 중에서 가장 많이 사용 되 는 종 류 는 AtomicInteger 입 니 다. 정수 로 볼 수 있 습 니 다. Integer 와 달리 가 변 적 이 고 스 레 드 가 안전 합 니 다. 모든 작업 은 CAS 명령 으로 이 루어 집 니 다.
AtomicInteger 의 사용법 은 매우 간단 하 다.
public class AtomicIntegerDemo {
    static AtomicInteger i = new AtomicInteger();

    public static class AddThread implements Runnable {

        @Override
        public void run() {
            for (int j = 0; j < 10000; j++) {
                i.incrementAndGet();//    1,     
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] ts = new Thread[10];
        for (int j = 0; j < 10; j++) {
            ts[j] = new Thread(new AddThread());
        }
        for (int j = 0; j < 10; j++) {
            ts[j].start();
        }
        for (int j = 0; j < 10; j++) {
            ts[j].join();
        }
        System.out.println(i);
    }
}

AtomicInteger 와 유사 한 종 류 는 다음 과 같다. AtomicLong 은 Long 형 데 이 터 를 대표 하고, AtomicBoolean 은 Boolean 형 데 이 터 를 표시 하 며, AtomicReference 는 대상 인용 을 나타 낸다.
3. 잠 금 없 는 대상 참조: AtomicReference
수정 대상 이 인용 할 때의 스 레 드 안전성 을 보장 할 수 있 습 니 다. 그러나 문제 가 있 습 니 다. 대상 의 현재 데 이 터 를 가 져 온 후 새 값 으로 수정 하려 고 준비 하기 전에 대상 의 값 은 다른 스 레 드 에 의 해 두 번 연속 으로 수정 되 었 습 니 다. 이 두 번 의 수정 을 거 친 후에 대상 의 값 이 다시 이전 값 으로 회복 되면 현재 스 레 드 는 이 대상 이 수정 되 었 는 지 여 부 를 정확하게 판단 할 수 없습니다.
예 를 들 어 다음 장면: 업 체 는 고객 을 만류 하기 위해 귀빈 카드 에 20 위안 이하 의 고객 에 게 한꺼번에 20 위안 을 증정 하기 로 결정 했다. 조건 은 모든 고객 에 게 한 번 만 증정 할 수 있다 는 것 이다.
만약 에 증 여 된 금액 이 입금 되 는 동시에 고객 이 한 번 의 소 비 를 해서 총 금액 이 20 위안 보다 적 고 소 비 된 금액 이 증 여 된 금액 전의 금액 과 같다 면 백 스테이지 에서 진행 하면 이 계좌 가 아직 증여되 지 않 았 다 고 착각 하여 오류 가 발생 할 수 있 습 니 다. 아래 절 차 는 이 장면 을 모 의 했 습 니 다.
public class AtomicReferenceDemo {

    static AtomicReference money = new AtomicReference();

    public static void main(String[] args) {
        money.set(19);
        //               ,     
        for (int i = 0; i < 3; i++) {
            new Thread() {
                @Override
                public void run() {
                    while (true) {
                        while (true) {
                            Integer m = money.get();
                            if (m < 20) {
                                if (money.compareAndSet(m, m + 20)) {
                                    System.out.println("    20 ,    ,  :" + money.get() + " ");
                                    break;
                                }
                            } else {
                                //System.out.println("    20 ,    ");
                                break;
                            }
                        }
                    }
                }
            }.start();
        }
        //      ,      
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    while (true) {
                        Integer m = money.get();
                        if (m > 10) {
                            System.out.println("  10 ");
                            if (money.compareAndSet(m, m - 10)) {
                                System.out.println("    10 ,  :" + money.get());
                                break;
                            }
                        } else {
                            System.out.println("       ");
                            break;
                        }
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

JDK 는 AtomicStamped Reference 를 사용 하면 이 문 제 를 해결 할 수 있다.
4. 타임스탬프 가 있 는 대상 참조: AtomicStamped Reference
AtomicReference 가 상기 문 제 를 해결 하지 못 하 는 이 유 는 대상 이 수정 과정 에서 상태 정 보 를 잃 어 버 렸 고 대상 값 자체 와 상태 에 등 호 를 그 렸 기 때문에 대상 이 수정 과정 에서 의 상태 값 만 기록 하면 이 문 제 를 해결 할 수 있 기 때문이다.
AtomicStamped Reference 내 부 는 대상 값 뿐만 아니 라 시간 스탬프 (또는 버 전 스탬프 라 고도 함) 도 유지 합 니 다. 시간 스탬프 가 바 뀌 면 부적 절 한 기록 을 방지 할 수 있 습 니 다. 주요 방법 은 다음 과 같 습 니 다.
//    ,       
public AtomicStampedReference(V initialRef, int initialStamp)
//    
public V getReference()
//     
public int getStamp()
//                            ,                 
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp)
//              ,            
public boolean attemptStamp(V expectedReference, int newStamp)
//              
public void set(V newReference, int newStamp) 

앞의 문 제 를 해결 하 다.
public class AtomicStampedReferenceDemo {

    static AtomicStampedReference money = new AtomicStampedReference<>(19,0);

    public static void main(String[] args) {
        //               ,     
        for (int i = 0; i < 3; i++) {
            final int timeStamp = money.getStamp();
            new Thread() {
                @Override
                public void run() {
                    while (true) {
                        while (true) {
                            Integer m = money.getReference();
                            if (m < 20) {
                                if (money.compareAndSet(m, m + 20,timeStamp,timeStamp+1)) {
                                    System.out.println("    20 ,    ,  :" + money.getReference() + " ");
                                    break;
                                }
                            } else {
                                //System.out.println("    20 ,    ");
                                break;
                            }
                        }
                    }
                }
            }.start();
        }
        //      ,      
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    while (true) {
                        int timeStamp = money.getStamp();
                        Integer m = money.getReference();
                        if (m > 10) {
                            System.out.println("  10 ");
                            if (money.compareAndSet(m, m - 10,timeStamp,timeStamp+1)) {
                                System.out.println("    10 ,  :" + money.getReference());
                                break;
                            }
                        } else {
                            System.out.println("       ");
                            break;
                        }
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

5. 배열 도 잠 금 없 음: AtomicIntegerArray
JDK 는 기본 데이터 형식 외 에 도 AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray 등 복합 구 조 를 제공한다.
간단 한 예:
public class AtomicIntegerArrayDemo {
    static AtomicIntegerArray array = new AtomicIntegerArray(10);

    public static class AddThread implements Runnable {

        @Override
        public void run() {
            //    10       1000 
            for (int i = 0; i < 10000; i++) {
                array.getAndIncrement(i % array.length());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //  10   
        Thread[] ts = new Thread[10];
        for (int i = 0; i < 10; i++) {
            ts[i] = new Thread(new AddThread());
        }
        for (int i = 0; i < 10; i++) {
            ts[i].start();
        }
        for (int i = 0; i < 10; i++) {
            ts[i].join();
        }
        System.out.println(array);
    }
}

좋은 웹페이지 즐겨찾기