자바 8 병행 튜 토리 얼: 원자 변수 와 ConcurrentMa
Java 8 Concurrency 튜 토리 얼: 동기 화 및 잠 금
비 룡
협의: CC BY - NC - SA 4.0
제 자바 8 다 중 스 레 드 프로 그래 밍 시리즈 의 세 번 째 부분 을 읽 어 주 십시오.이 튜 토리 얼 은 병렬 API 의 두 가지 중요 한 부분 을 포함한다. 원자 변수 와
ConcurrentMap
.최근 발 표 된 자바 8 의 lambda 표현 식 과 함수 식 프로 그래 밍 으로 인해 둘 다 크게 개선 되 었 습 니 다.모든 새로운 기능 은 간단 하고 알 기 쉬 운 코드 예제 로 설명 할 것 이다.마음 에 드 셨 으 면 좋 겠 습 니 다.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
, AtomicLong
와 AtomicReference
가 있다.LongAdder
LongAdder
는 AtomicLong
의 대체 로 특정한 수치 에 연속 으로 값 을 추가 하 는 데 사용 된다.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()
LongAdder
는 LongAccumulator
보다 통용 되 는 버 전이 다.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()
외 에 도 두 개의 변형 이 있다. BiFunction
과 compute()
.이 방법의 함수 식 인 자 는 키 가 존재 하지 않 거나 존재 할 때 만 호출 됩 니 다.마지막 으로
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
, ConcurrentMap
과 forEach
.이 작업 들 은 각각 네 가지 형식 으로 제공 되 며 키, 값, 요소 또는 키 값 을 매개 변수 로 하 는 함 수 를 받 아들 입 니 다.이 모든 방법의 첫 번 째 매개 변 수 는 통용 된다
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 내 창 고 를 환영 하 며 직접 시도 해 보 세 요.
만약 당신 이 나의 일 을 지지 하고 싶다 면, 당신 의 친구 에 게 이 강 좌 를 공유 하 세 요.너 도 트 위 터 에서 나 를 주목 할 수 있다. 왜냐하면 나 는 자바 나 프로 그래 밍 과 관련 된 것 을 계속 푸 시 할 것 이기 때문이다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
【Liquibase】DB 작성·테이블 정의 변경신규 스타터 프로젝트 작성 Liquibase와 MySQL 선택 application.properties에 DB 정보 넣기 MySQL에서 "testdatabase"라는 데이터베이스 만들기 빌드 종속성 추가 build....
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.