자바 8 병행 튜 토리 얼: 원자 변수 와 ConcurrentMa

11741 단어 java8자바
자바 8 병행 튜 토리 얼: 원자 변수 와 ConcurrentMap
Java 8 Concurrency 튜 토리 얼: 동기 화 및 잠 금
비 룡
협의: CC BY - NC - SA 4.0
제 자바 8 다 중 스 레 드 프로 그래 밍 시리즈 의 세 번 째 부분 을 읽 어 주 십시오.이 튜 토리 얼 은 병렬 API 의 두 가지 중요 한 부분 을 포함한다. 원자 변수 와 ConcurrentMap.최근 발 표 된 자바 8 의 lambda 표현 식 과 함수 식 프로 그래 밍 으로 인해 둘 다 크게 개선 되 었 습 니 다.모든 새로운 기능 은 간단 하고 알 기 쉬 운 코드 예제 로 설명 할 것 이다.마음 에 드 셨 으 면 좋 겠 습 니 다.
  • 첫 번 째 부분: 스 레 드 와 실행 기
  • 두 번 째 부분: 동기 화 와 자물쇠
  • 세 번 째 부분: 원자 변수 와 ConcurrentMap
  • 간단 한 요소 에서 이 튜 토리 얼 의 코드 예 는 여기에 정 의 된 두 개의 보조 함수 sleep(seconds)stop(executor) 를 사용 했다.AtomicInteger java.concurrent.atomic 가방 은 많은 실 용적 인 종 류 를 포함 하여 원자 조작 을 수행 하 는 데 사용 된다.만약 당신 이 다 중 스 레 드 에서 어떤 조작 을 동시에 안전하게 수행 할 수 있다 면 synchronized 키워드 나 이전 장의 자물쇠 가 필요 하지 않 습 니 다. 그러면 이 조작 은 원자 입 니 다.
    본질 적 으로 원자 조작 은 비교 와 교환 (CAS) 에 심각하게 의존 하 는데 이것 은 대부분의 현대 CPU 가 직접 지원 하 는 원자 명령 이다.이 명령 들 은 보통 동기 블록 보다 빠르다.따라서 하나의 가 변 변 변 수 를 동시에 수정 해 야 하 는 상황 에서 저 는 지난 장 에 보 여 준 자물쇠 가 아 닌 원자 류 를 우선 사용 하 는 것 을 권장 합 니 다.
    번역자 주: 다른 언어 에 대해 일부 언어의 원자 조작 작용 잠 금 은 원자 명령 이 아니 라 실현 된다.
    지금 우 리 는 원자 류 를 선택 합 니 다. 예 를 들 어 AtomicInteger:
    AtomicInteger atomicInt = new AtomicInteger(0);
    
    ExecutorService executor = Executors.newFixedThreadPool(2);
    
    IntStream.range(0, 1000)
        .forEach(i -> executor.submit(atomicInt::incrementAndGet));
    
    stop(executor);
    
    System.out.println(atomicInt.get());    // => 1000
    AtomicInteger 대체 Integer 를 사용 하면 동기 화 접근 변 수 를 필요 로 하지 않 고 안전하게 수 치 를 증가 시 킬 수 있 습 니 다.incrementAndGet() 방법 은 원자 조작 이기 때문에 우 리 는 여러 라인 에서 안전하게 그것 을 호출 할 수 있다.AtomicInteger 다양한 원자 조작 을 지원 합 니 다.updateAndGet() 정수 에서 임의의 동작 을 수행 할 수 있 도록 lambda 표현 식 을 받 아들 입 니 다.
    AtomicInteger atomicInt = new AtomicInteger(0);
    
    ExecutorService executor = Executors.newFixedThreadPool(2);
    
    IntStream.range(0, 1000)
        .forEach(i -> {
            Runnable task = () ->
                atomicInt.updateAndGet(n -> n + 2);
            executor.submit(task);
        });
    
    stop(executor);
    
    System.out.println(atomicInt.get());    // => 2000
    accumulateAndGet() 방법 은 다른 유형 IntBinaryOperator 의 lambda 표현 식 을 받 아들 입 니 다.우 리 는 다음 예 에서 이 방법 을 사용 하여 0 ~ 1000 모든 값 의 합 을 동시에 계산한다.
    AtomicInteger atomicInt = new AtomicInteger(0);
    
    ExecutorService executor = Executors.newFixedThreadPool(2);
    
    IntStream.range(0, 1000)
        .forEach(i -> {
            Runnable task = () ->
                atomicInt.accumulateAndGet(i, (n, m) -> n + m);
            executor.submit(task);
        });
    
    stop(executor);
    
    System.out.println(atomicInt.get());    // => 499500

    기타 실 용적 인 원자 류 는 AtomicBoolean, AtomicLongAtomicReference 가 있다.LongAdder LongAdderAtomicLong 의 대체 로 특정한 수치 에 연속 으로 값 을 추가 하 는 데 사용 된다.
    ExecutorService executor = Executors.newFixedThreadPool(2);
    
    IntStream.range(0, 1000)
        .forEach(i -> executor.submit(adder::increment));
    
    stop(executor);
    
    System.out.println(adder.sumThenReset());   // => 1000
    LongAdder 원자 수치 류 처럼 똑 같이 스 레 드 가 안전 하 다.그러나 이 종 류 는 내부 에서 일련의 변 수 를 유지 하여 스 레 드 간 의 경쟁 을 줄 이 는 것 이지 단일 결 과 를 구 하 는 것 이 아니다.실제 결 과 는 호출 add() 또는 increment() 을 통 해 얻 을 수 있다.
    다 중 스 레 드 의 업데이트 가 읽 는 것 보다 더 잦 을 때 이 종 류 는 보통 원자 수치 류 보다 성능 이 좋 습 니 다.이러한 상황 은 통계 데 이 터 를 캡 처 할 때 자주 발생 합 니 다. 예 를 들 어 웹 서버 에서 요청 한 수량 을 통계 하고 싶 습 니 다.sum() 메모리 에 일련의 변 수 를 저장 하기 때문에 비교적 높 은 메모리 비용 이 단점 이다.sumThenReset() LongAdderLongAccumulator 보다 통용 되 는 버 전이 다.LongAccumulator 형식 LongAdder lambda 표현 식 으로 구축 되 었 습 니 다. 덧셈 만 수행 하 는 것 이 아니 라 이 코드 가 보 여 준 것 처럼:
    LongBinaryOperator op = (x, y) -> 2 * x + y;
    LongAccumulator accumulator = new LongAccumulator(op, 1L);
    
    ExecutorService executor = Executors.newFixedThreadPool(2);
    
    IntStream.range(0, 10)
        .forEach(i -> executor.submit(() -> accumulator.accumulate(i)));
    
    stop(executor);
    
    System.out.println(accumulator.getThenReset());     // => 2539

    우 리 는 함수 LongAccumulator 를 사용 하여 LongBinaryOperator 를 만 들 었 고 초기 값 은 1 입 니 다.호출 2 * x + y 할 때마다 현재 결과 와 값 LongAccumulator 은 매개 변수 로 lambda 표현 식 에 전 달 됩 니 다.accumulate(i) i 처럼 내부 에서 일련의 변 수 를 유지 하여 스 레 드 간 의 경쟁 을 줄인다.LongAccumulator LongAdder 인 터 페 이 스 는 ConcurrentMap 인 터 페 이 스 를 계승 하고 가장 실 용적 인 병렬 집합 유형 중 하 나 를 정의 했다.자바 8 은 새로운 방법 을 이 인터페이스 에 추가 하여 함수 식 프로 그래 밍 을 도입 했다.
    다음 코드 에서 우 리 는 이 맵 예제 를 사용 하여 새로운 방법 을 보 여 줍 니 다.
    ConcurrentMap map = new ConcurrentHashMap<>();
    map.put("foo", "bar");
    map.put("han", "solo");
    map.put("r2", "d2");
    map.put("c3", "p0");
    ConcurrentMap 방법 은 형식 Map 의 lambda 표현 식 을 받 아들 여 매 핑 된 키 와 값 을 매개 변수 로 전달 합 니 다.그것 은 forEach() 순환 의 대체 로 서 맵 의 요 소 를 옮 겨 다 닐 수 있다.현재 스 레 드 에서 직렬 로 반복 합 니 다.
    map.forEach((key, value) -> System.out.printf("%s = %s
    ", key, value));

    새로운 방법 BiConsumer 은 제 공 된 키 가 존재 하지 않 을 때 만 새로운 값 을 맵 에 추가 합 니 다.적어도 for-each 의 실현 에서 이 방법 은 putIfAbsent() 처럼 스 레 드 가 안전 하기 때문에 서로 다른 스 레 드 에서 동시 방문 매 핑 을 할 때 동기 화 메커니즘 이 필요 하지 않 습 니 다.
    String value = map.putIfAbsent("c3", "p1");
    System.out.println(value);    // p0
    ConcurrentHashMap 방법 은 지정 한 키 의 값 을 되 돌려 줍 니 다.들 어 오 는 키 가 존재 하지 않 을 때 기본 값 으로 돌아 갑 니 다:
    String value = map.getOrDefault("hi", "there");
    System.out.println(value);    // there
    put() 수용 유형 getOrDefault() 의 lambda 표현 식 입 니 다.replaceAll() 두 개의 인 자 를 받 아들 이 고 값 을 되 돌려 줍 니 다.함 수 는 여기 서 모든 요소 의 키 와 값 으로 호출 되 고 현재 키 에 비 칠 새 값 을 되 돌려 줍 니 다.
    map.replaceAll((key, value) -> "r2".equals(key) ? "d3" : value);
    System.out.println(map.get("r2"));    // d3
    BiFunction 맵 의 모든 값 을 바 꾸 는 것 이 아니 라 하나의 요 소 를 바 꿀 수 있 습 니 다.이 방법 은 처리 해 야 할 키 와 지정 한 값 의 변환 에 사용 되 는 BiFunction 을 받 아들 입 니 다.
    map.compute("foo", (key, value) -> value + value);
    System.out.println(map.get("foo"));   // barbar
    compute() 외 에 도 두 개의 변형 이 있다. BiFunctioncompute().이 방법의 함수 식 인 자 는 키 가 존재 하지 않 거나 존재 할 때 만 호출 됩 니 다.
    마지막 으로 computeIfAbsent() 방법 은 맵 의 기 존 값 으로 새로운 값 을 통일 하 는 데 사용 할 수 있다.이 방법 은 키 를 받 아들 이 고 기 존 요소 의 새로운 값 과 두 값 의 합병 행 위 를 지정 해 야 합 니 다 computeIfPresent().
    map.merge("foo", "boo", (oldVal, newVal) -> newVal + " was " + oldVal);
    System.out.println(map.get("foo"));   // boo was foo
    merge()
    이 모든 방법 은 BiFunction 인터페이스의 일부분 이기 때문에 이 인터페이스의 실현 에 있어 서 호출 할 수 있다.그 밖 에 가장 중요 한 실현 ConcurrentHashMap 은 새로운 방법 으로 개선 하여 맵 에서 병행 작업 을 수행 할 수 있 도록 했다.
    병렬 흐름 처럼 이 방법 들 은 특정한 ConcurrentMap 을 사용 하여 자바 8 의 ConcurrentHashMap 에서 제공 합 니 다.이 풀 은 사용 가능 한 핵심 수량 에 달 려 있 는 프 리 셋 병행 체 제 를 사용 했다.내 컴퓨터 는 네 개의 핵심 을 사용 할 수 있다. 이것 은 병행 적 인 결 과 를 3 으로 만 들 것 이다.
    System.out.println(ForkJoinPool.getCommonPoolParallelism());  // 3

    이 값 은 다음 JVM 인 자 를 설정 하여 증감 할 수 있 습 니 다.
    -Djava.util.concurrent.ForkJoinPool.common.parallelism=5

    우 리 는 똑 같은 매 핑 예 시 를 사용 하여 보 여 주 었 으 나 이번 에는 ForkJoinPool 인터페이스 가 아 닌 구체 적 인 ForkJoinPool.commonPool() 인 터 페 이 스 를 사용 하여 이 유형의 모든 공공 방법 을 방문 할 수 있 습 니 다.
    ConcurrentHashMap map = new ConcurrentHashMap<>();
    map.put("foo", "bar");
    map.put("han", "solo");
    map.put("r2", "d2");
    map.put("c3", "p0");

    자바 8 은 세 가지 유형의 병행 작업 을 도입 했다. ConcurrentHashMap, ConcurrentMapforEach.이 작업 들 은 각각 네 가지 형식 으로 제공 되 며 키, 값, 요소 또는 키 값 을 매개 변수 로 하 는 함 수 를 받 아들 입 니 다.
    이 모든 방법의 첫 번 째 매개 변 수 는 통용 된다 search.이 한도 값 은 작업 을 병행 할 때의 최소 집합 크기 를 나타 낸다.예 를 들 어 한도 값 500 을 입력 하고 맵 의 실제 크기 가 499 이면 작업 은 단일 스 레 드 에서 직렬 로 실 행 됩 니 다.다음 예 에서 우 리 는 한도 값 1 을 사용 하여 항상 강제 병행 으로 보 여 줍 니 다.reduce parallelismThreshold 방법 은 반복 맵 의 키 값 을 병행 할 수 있 습 니 다.forEach 현재 교체 요소 의 키 와 값 으로 호출 합 니 다.병렬 실행 을 시각 화하 기 위해 서 현재 스 레 드 의 이름 을 콘 솔 에 인쇄 했 습 니 다.내 밑 에 있 는 forEach() 에서 최대 세 개의 스 레 드 를 사용 하 는 것 을 주의해 야 한다.
    map.forEach(1, (key, value) ->
        System.out.printf("key: %s; value: %s; thread: %s
    ", key, value, Thread.currentThread().getName())); // key: r2; value: d2; thread: main // key: foo; value: bar; thread: ForkJoinPool.commonPool-worker-1 // key: han; value: solo; thread: ForkJoinPool.commonPool-worker-2 // key: c3; value: p0; thread: main
    BiConsumer ForkJoinPool 방법 은 search 을 받 아들 이 고 현재 키 값 을 빈 검색 결 과 를 되 돌려 주거 나 현재 교체 가 검색 조건 과 일치 하지 않 을 때 되 돌려 줍 니 다 search().비어 있 지 않 은 결 과 를 되 돌려 주기 만 하면 아래로 수색 하지 않 을 것 이다.BiFunction 무질서 하 다 는 것 을 기억 해 야 한다.검색 함 수 는 실제 처리 순서 에 의존 하지 않 아야 합 니 다.맵 의 여러 요소 가 지정 한 검색 함 수 를 만족 시 키 면 결 과 는 불확실 합 니 다.
    String result = map.search(1, (key, value) -> {
        System.out.println(Thread.currentThread().getName());
        if ("foo".equals(key)) {
            return value;
        }
        return null;
    });
    System.out.println("Result: " + result);
    
    // ForkJoinPool.commonPool-worker-2
    // main
    // ForkJoinPool.commonPool-worker-3
    // Result: bar

    다음은 다른 예 입 니 다. 맵 의 값 만 검색 합 니 다.
    String result = map.searchValues(1, value -> {
        System.out.println(Thread.currentThread().getName());
        if (value.length() > 3) {
            return value;
        }
        return null;
    });
    
    System.out.println("Result: " + result);
    
    // ForkJoinPool.commonPool-worker-2
    // main
    // main
    // ForkJoinPool.commonPool-worker-1
    // Result: solo
    null ConcurrentHashMap 방법 은 자바 8 의 데이터 흐름 에서 사용 되 었 습 니 다. 두 가지 reduce 형식의 lambda 표현 식 을 받 아들 입 니 다.첫 번 째 함 수 는 모든 키 값 을 임의의 형식의 단일 값 으로 변환 합 니 다.두 번 째 함 수 는 이 모든 변 환 된 값 을 단일 결과 로 조합 하고 가능 한 모든 값 reduce() 을 무시 합 니 다.
    String result = map.reduce(1,
        (key, value) -> {
            System.out.println("Transform: " + Thread.currentThread().getName());
            return key + "=" + value;
        },
        (s1, s2) -> {
            System.out.println("Reduce: " + Thread.currentThread().getName());
            return s1 + ", " + s2;
        });
    
    System.out.println("Result: " + result);
    
    // Transform: ForkJoinPool.commonPool-worker-2
    // Transform: main
    // Transform: ForkJoinPool.commonPool-worker-3
    // Reduce: ForkJoinPool.commonPool-worker-3
    // Transform: main
    // Reduce: main
    // Reduce: main
    // Result: r2=d2, c3=p0, han=solo, foo=bar

    나 는 네가 나의 자바 8 병발 시리즈 튜 토리 얼 의 세 번 째 부분 을 좋아 할 수 있 기 를 바란다.이 튜 토리 얼 의 코드 예제 는 Github 에 위탁 되 어 있 고 다른 자바 8 코드 세 션 도 많다.fork 내 창 고 를 환영 하 며 직접 시도 해 보 세 요.
    만약 당신 이 나의 일 을 지지 하고 싶다 면, 당신 의 친구 에 게 이 강 좌 를 공유 하 세 요.너 도 트 위 터 에서 나 를 주목 할 수 있다. 왜냐하면 나 는 자바 나 프로 그래 밍 과 관련 된 것 을 계속 푸 시 할 것 이기 때문이다.
  • 첫 번 째 부분: 스 레 드 와 실행 기
  • 두 번 째 부분: 동기 화 와 자물쇠
  • 세 번 째 부분: 원자 변수 와 ConcurrentMap
  • 좋은 웹페이지 즐겨찾기