6. AQS 및 동기 화 구성 요소

1. AQS 소개
AQS 는 JUC 의 핵심 이다.JUC 는 JAVA 의 병발 성능 을 크게 향상 시 켰 다.
  • 바 텀 데이터 구 조 는 FIFO 대기 열 이다.
  • Node 를 사용 하여 FIFO 대기 열 을 실현 하고 자물쇠 나 다른 동기 장치 의 기본 프레임 워 크 를 구축 할 수 있 습 니 다.
  • int 유형 을 이용 하여 상 태 를 나 타 냈 다.
  • 사용 방법 은 계승 이다. 자 류 는 계승 을 통 해 그 상 태 를 관리 하 는 방법 이다.
  • 배타 적 자물쇠 와 공유 자물쇠 모드 를 동시에 실현 할 수 있다.사용자 의 측면 에서 AQS 의 기능 은 두 가지 로 나 뉘 는데 독점 기능 과 공유 기능 이다. 모든 하위 클래스 에서 독점 기능 API 를 실현 하고 사 용 했 거나 공유 잠 금 기능 을 사 용 했 거나 API 두 세트
  • 를 동시에 사용 하지 않 는 다.
    그 밑 에 있 는 것 은 양 방향 목록 을 사용 하 는 것 이 고 대열 의 실현 이기 때문에 이것 을 하나의 대열 로 간주 할 수도 있다. 그 중에서 Sync queue 는 동기 화 목록 이 고 양 방향 목록 으로 head, tail 노드 를 포함한다. 그 중에서 head 노드 는 주로 후속 적 인 스케줄 링 에 사용 된다.Condition queue 는 단 방향 링크 입 니 다. 필요 한 것 이 아 닙 니 다. 프로그램 에 Condition 이 필요 할 때 만 이 단 방향 링크 가 존재 하고 여러 Condition queue 가 있 을 수 있 습 니 다.
    AQS 가 실현 하 는 대체적인 사고방식: 먼저 AQS 내부 에서 CLH 대기 열 을 유지 하여 자 물 쇠 를 관리 합 니 다. 스 레 드 는 먼저 자 물 쇠 를 가 져 오 려 고 시도 합 니 다. 실패 하면 현재 스 레 드 와 대기 상태 등 정 보 를 NODE 노드 로 묶 어서 동기 화 대기 열 (Sync queue) 에 추가 합 니 다. 그 다음 에 계속 순환 을 따라 자 물 쇠 를 가 져 오 려 고 시도 합 니 다. 그 조건 은 현재 노드 가 head 인 직접적인 후계 가 되 어야 시도 할 수 있 습 니 다.실패 하면 자신 이 깨 어 날 때 까지 자신 을 막 고 자 물 쇠 를 가 진 스 레 드 가 자 물 쇠 를 풀 때 대기 열 에 있 는 후계 스 레 드 를 깨 웁 니 다.
    2. AQS 동기 화 구성 요소
    2.1 CountDownLatch
    계수 기 는 0 으로 줄 여야 기다 리 는 스 레 드 에서 계속 실 행 됩 니 다.한 번 만 사용 할 수 있 고 리 셋 할 수 없습니다.
    예 를 들 어 연 산 량 이 매우 많은 임무 가 있 는데 우 리 는 그것 을 여러 개의 하위 임무 로 나 누 어 모든 하위 임 무 를 완성 한 후에 마지막 총 작업 을 집행 할 수 있다.
    @Slf4j
    public class CountdownLatchTest {
    
        private static int threadCount = 200;
    
        public static void main(String[] args) {
            ExecutorService exec = Executors.newCachedThreadPool();
            final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for(int i=0;i(() -> {
                    try{
                        test(threadNum);
                    }catch (Exception e){
                        log.error("exection",e);
                    }finally {
                        countDownLatch.countDown();
                    }
                });
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("finish ...");
            exec.shutdown();
        }
    
        private static void test(int threadNum) {
            log.info("{}",threadNum);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    실행 결과 마지막 점 을 캡 처 했 습 니 다:
    16:34:36.385 [pool-1-thread-90] INFO com.njupt.swg.threadstudyjimin.item07.CountdownLatchTest - 89
    16:34:36.385 [pool-1-thread-84] INFO com.njupt.swg.threadstudyjimin.item07.CountdownLatchTest - 83
    16:34:36.385 [pool-1-thread-92] INFO com.njupt.swg.threadstudyjimin.item07.CountdownLatchTest - 91
    16:34:36.385 [pool-1-thread-94] INFO com.njupt.swg.threadstudyjimin.item07.CountdownLatchTest - 93
    16:34:36.385 [pool-1-thread-96] INFO com.njupt.swg.threadstudyjimin.item07.CountdownLatchTest - 95
    16:34:36.385 [pool-1-thread-98] INFO com.njupt.swg.threadstudyjimin.item07.CountdownLatchTest - 97
    16:34:36.386 [pool-1-thread-100] INFO com.njupt.swg.threadstudyjimin.item07.CountdownLatchTest - 99
    16:34:36.516 [main] INFO com.njupt.swg.threadstudyjimin.item07.CountdownLatchTest - finish ...

    그렇다면 이런 장면 이 라면 몇 개의 키 임 무 를 계산 하고 시간 을 정 해 이 시간 을 초과 하면 이 임 무 를 포기 하 라.
    countDownLatch.await(10, TimeUnit.MILLISECONDS);

    2.2 Semaphore
    같은 시간 에 스 레 드 의 수 를 조절 할 수 있 습 니 다.
    Semaphore 가 실현 하 는 기능 은 화장실 에 5 개의 구덩이 가 있 는 것 과 유사 하 다. 만약 10 명 이 화장실 에 가 려 고 한다 면 동시에 몇 명 만 화장실 에 갈 수 있 을 까?동시에 5 명 만 점용 할 수 있 고 5 명 중 한 명 이 비 키 면 그 중 기다 리 는 다른 5 명 중 한 명 이 점용 할 수 있다.또한 기다 리 는 5 명 중 무 작위 로 우선 기 회 를 얻 을 수도 있 고 선착순 으로 기 회 를 얻 을 수도 있 습 니 다. 이 는 Semaphore 대상 을 구성 할 때 들 어 오 는 매개 변수 옵션 에 달 려 있 습 니 다.단일 신 호 량 의 Semaphore 대상 은 상호 배척 자물쇠 의 기능 을 실현 할 수 있 고 한 라인 에서 '자물쇠' 를 얻 을 수 있 으 며 다른 라인 에서 '자물쇠' 를 방출 할 수 있다. 이것 은 자물쇠 복구 의 일부 장소 에 응용 할 수 있다.
    @Slf4j
    public class SemaphoreLatchTest {
    
        private static int threadCount = 20;
    
        public static void main(String[] args) {
            ExecutorService exec = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(3);
            for(int i=0;ifinal int threadNum = i;
                exec.execute(() -> {
                    try{
                        semaphore.acquire();//      
                        test(threadNum);
                        semaphore.release();//      
                    }catch (Exception e){
                        log.error("exection",e);
                    }
                });
            }
            log.info("finish ...");
            exec.shutdown();
        }
    
        private static void test(int threadNum) throws InterruptedException {
            Thread.sleep(1000);
            log.info("{}",threadNum);
        }
    }

    실행 결 과 는 1 초 에 세 개의 스 레 드 를 실행 합 니 다.여 기 는 하나의 스 레 드 로 허 가 를 받 으 면 같은 시간 에 세 개의 스 레 드 가 들 어 와 함께 일 할 수 있 습 니 다.그럼 제 가 하나의 스 레 드 로 바 꿔 서 세 가지 허 가 를 받 으 면 요?한 사람 이 세 개의 구 덩이 를 동시에 차지 하 는 것 처럼 이 사람 이 다 당 겨 야 다음 사람 차례 가 올 수 있다. 그러면 이 때 는 하나의 스 레 드 와 같다.
    semaphore.acquire(3);
    test(threadNum);
    semaphore.release(3);

    이 장면 을 고려 해 보 자. 병발 이 너무 높 아서 라인 의 수량 을 조절 하 더 라 도 비교적 까다롭다.화장실 하나 에 구덩이 가 세 개 있 고 밖 에 사람 이 너무 많아 서 세 사람 을 들 어 오 게 하고 다른 사람 은 모두 쫓 아 냈 다.어떻게 할 까요?
    if(semaphore.tryAcquire()){//        
        test(threadNum);
        semaphore.release();
    }

    출력 결과: 세 개의 정보 만 인쇄 되 고 다른 스 레 드 는 모두 버 려 집 니 다.
    시간 초과 도 줄 수 있 고 여 기 는 5000 밀리초 입 니 다.명령 마다 1000 밀리초 를 실행 해 야 합 니 다. 그러면 프로그램 은 1000 밀리초 후에 세 개 를 인쇄 합 니 다.그리고 1000 밀리초 만 더 기다 리 면 새로운 세 가지 허 가 를 받 고 세 가 지 를 인쇄 할 수 있 습 니 다.5000 밀리초 다 쓸 때 까지3 * 5 개의 기록 을 인쇄 할 수 있 습 니 다.나머지 5 개 기록 은 시간 초과 로 모두 포기 됐다.
    화장실 에 가 는 예: 모두 세 개의 구덩이 입 니 다. 저 는 한 시간 을 정 했 습 니 다. 먼저 세 사람 이 들 어 왔 습 니 다. 자, 그들 은 모두 10 분 안에 해결 합 니 다. (여기 서 마지막 사람 이 딱 10 분 이 걸 렸 다 고 가정 합 니 다) 그러면 이 세 개의 구 덩이 는 계속 사용 할 수 있 습 니 다. 다음 세 사람 이 들 어 와 서 사용 하고 가설 은 마침 10 분 이 걸 렸 습 니 다.그럼 한 시간 까지 몇 명 이 화장실 에 갈 수 있 습 니까?
    if(semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)){
        test(threadNum);
        semaphore.release();
    }

    2.3 CyclicBarrier
    Cyclic Barrier 도 동기 화 보조 클래스 입 니 다. 하나의 스 레 드 가 서로 기다 릴 수 있 습 니 다. 특정한 공공 장벽 에 도착 할 때 까지 여러 스 레 드 간 에 서로 기다 릴 수 있 습 니 다. 모든 스 레 드 가 준 비 된 후에 야 각자 후속 작업 을 계속 할 수 있 습 니 다. Count Downlatch 와 비슷 한 곳 은 바로..이것 도 계수 기 를 통 해 이 루어 집 니 다. 어떤 스 레 드 가 await () 방법 을 호출 한 후에 이 스 레 드 는 대기 상태 에 들 어 갔습니다. 그리고 계수 기 는 - 1 작업 을 합 니 다. 계수기 의 값 이 우리 가 설정 한 초기 값 0 에 이 르 렀 을 때 이전에 await () 를 호출 했 습 니 다.방법 은 대기 상태 에 들 어간 스 레 드 가 깨 어 나 후속 작업 을 계속 수행 합 니 다. Cyclicbarrier 가 스 레 드 를 방출 한 후에 다시 사용 할 수 있 기 때문에 순환 장벽 이 라 고도 합 니 다. Cyclicbarrier 의 사용 장면 은 CountDownlatch 와 비슷 하여 다 중 스 레 드 로 데 이 터 를 계산 하고 결 과 를 합 친 응용 장면 입 니 다.
    둘 의 차이:
  • Countdown Latch 의 계수 기 는 한 번 만 사용 할 수 있 으 며, CyclicBarrier 의 계수 기 는 reset 리 셋 순환 으로 사용 할 수 있 습 니 다
  • Countdown Latch 는 주로 1 개 또는 n 개의 스 레 드 로 다른 스 레 드 가 특정한 작업 을 완성 한 후에 야 계속 아래로 실 행 될 수 있 습 니 다. 1 개 또는 n 개의 스 레 드 와 다른 스 레 드 의 관 계 를 묘사 합 니 다.Cyclic Barrier 는 주로 1 개 또는 여러 개의 스 레 드 간 의 상호 기다 림 을 실현 하고 모든 스 레 드 가 조건 을 만족 시 킨 후에 야 후속 작업 을 수행 합 니 다. 내부 각 스 레 드 가 서로 기다 리 는 관 계 를 묘사 합 니 다.
  • Cyclic Barrier 에 5 개의 스 레 드 가 await () 방법 을 호출 했다 면 이 5 개의 스 레 드 는 기다 리 고 있 습 니 다. 이 5 개의 스 레 드 가 모두 준 비 된 후에 각각 아래로 계속 실 행 됩 니 다. 만약 에 이 5 개의 스 레 드 가 다음 에 계산 에 오류 가 발생 하면 계산 기 를 리 셋 하고 이 5 개의 스 레 드 를 다시 실행 할 수 있 습 니 다.
    @Slf4j
    public class CyclicBarrierTest {
        private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
    
        public static void main(String[] args) throws InterruptedException {
            ExecutorService exec = Executors.newCachedThreadPool();
            for(int i=0;i<6;i++){
                Thread.sleep(1000 );
                exec.execute(() -> {
                    try{
                        race();
                    }catch (Exception e){
                        log.error("exception",e);
                    }
                });
            }
            exec.shutdown();
        }
    
        private static void race() throws InterruptedException {
            Thread.sleep(1000);
            log.info("ready...");
            try {
                cyclicBarrier.await();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            log.info("continue...");
        }
    }

    실행 효과: 먼저 레이스 방법 을 1 초 간격 으로 실행 하여 ready 를 출력 합 니 다. 3 개의 스 레 드 인쇄 가 끝나 면 바로 막 힌 log. info ("contine...");모두 인쇄 하 다.
    [pool-1-thread-1] INFO ready...
    [pool-1-thread-2] INFO ready...
    [pool-1-thread-3] INFO ready...
    [pool-1-thread-1] INFO continue...
    [pool-1-thread-2] INFO continue...
    [pool-1-thread-3] INFO continue...
    [pool-1-thread-4] INFO ready...
    [pool-1-thread-2] INFO ready...
    [pool-1-thread-3] INFO ready...
    [pool-1-thread-4] INFO continue...
    [pool-1-thread-2] INFO continue...
    [pool-1-thread-3] INFO continue...

    시간 초과 설정 도 할 수 있 고 시간 초과 하면 기다 리 지 않 는 다.
    private static void race() throws InterruptedException {
        Thread.sleep(1000);
        log.info("ready...");
        try {
            cyclicBarrier.await(2000, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.warn("exception",e);
        }
        log.info("continue...");
    }

    모두 가 준비 가 다 되 었 을 때, 먼저 한 가지 일 을 한다 면, Cyclic Barrier 를 설명 한 후에 스 레 드 를 추가 해서 실행 할 수 있 습 니 다.
    마치 회의 가 있 는 것 처럼 사람들 이 모두 도착 한 후에 우 리 는 소 리 를 질 렀 다. 사람들 이 모두 도착 했다. 우 리 는 지금 회 의 를 시작 했다.본 격 적 인 회 의 를 시작 하 겠 습 니 다.
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5,() -> {
        log.info("callback is running...");
    });

    3. ReentrantLock 과 자물쇠
    3.1 ReentrantLock 과 synchronized 의 차이
  • 모두 재 접속 가능 자물쇠
  • synchronized 는 JVM 이 실 현 했 고 ReentrantLock 은 jdk 가 실현 했다.전 자 는 운영 체제 로 이 루어 지고 후 자 는 코드 를 두 드 려 서 이 루어 진다.
  • synchronized 는 예전 에 성능 이 매우 나 빴 고 최적화 한 후에 이들 의 차이 가 많 지 않 았 다.
  • synchronized 가 편리 하고 ReentrantLock 은 수 동 으로 자 물 쇠 를 채 우 고 자 물 쇠 를 풀 어야 합 니 다.하지만 ReentrantLock 은 더욱 유연 하 다.

  • 3.2 ReentrantLock 의 장점
  • 공정 자물쇠 와 불공 정 자 물 쇠 를 지정 할 수 있 지만 synchronized 는 불공 정 자물쇠
  • 만 지정 할 수 있 습 니 다.
  • Condition 류 를 제공 하여 깨 워 야 할 스 레 드 를 그룹 으로 나 누 어 깨 울 수 있 습 니 다.synchronized 는 무 작위 로 스 레 드 를 깨 우거 나 모든 스 레 드 를 깨 웁 니 다
  • 잠 금 을 기다 리 는 스 레 드 를 중단 할 수 있 는 메커니즘 을 제공 합 니 다. lock. lockInterruptibly ()
  • ReentrantLock 은 자 회전 자물쇠 에 의 해 이 루어 지고 CAS 작업 을 순환 적 으로 호출 하여 자 물 쇠 를 추가 합 니 다. 성능 이 비교적 좋 은 것 은 스 레 드 가 커 널 상태 로 들 어 가 는 차단 상태
  • 를 피 했 기 때 문 입 니 다.
    이 가운데 ReentrantReadWriteLock 은 읽 기와 쓰기 자물쇠 가 없 는 상태 에서 만 기록 자 물 쇠 를 가 질 수 있다.많이 읽 고 적 게 쓰 면 쓰기 가 배 고 플 수 있다.

    좋은 웹페이지 즐겨찾기