Search

Item 81. wait와 notify보다는 동시성 유틸리티를 애용하라

생성일
2023/08/23 03:07
챕터
11장 -동시성

요약

wait와 notify는 올바르게 사용하기에 까다롭기 때문에 동시성 유틸리티를 사용하자.

동시성 유틸리티를 사용하자

이전 버전의 자바에서 wait와 notify를 사용하던 것들은 java.utill.concurrent 패키지의 동시성 유틸리티들로 쉽고 안전하게 대신 처리할 수 있다.
기존의 wait와 notify는 올바르게 사용하기에 아주 까다롭기 때문에, 고수준 동시성 유틸리티를 사용하자.
java.utill.concurrrent 패키지의 고수준 동시성 유틸리티는 크게 세 범주로 나누어 진다.
실행자 프레임워크(Executor)
기존의 Thread 클래스에서 하던 스레드 관리나 작업 처리를 실행자를 통해 쉽고 안전하게 처리할 수 있다.
동시성 프레임워크(concurrent collection)
동시성을 보장하는 컬렉션들을 사용하여 동시성 처리를 따로 하지 않고도 스레드-안전하며 간편하게 사용할 수 있다.
동기화 장치(synchronizer)
스레드가 다른 스레드를 기다릴 수 있게 만들어 서로의 작업을 조율할 수 있게 만들 수 있다.

동시성 프레임워크

동시성 컬렉션은 List, Oueue, Map 같은 표준 컬렉션 인터페이스에 동시성을 가미해 구현한 고성능 컬렉션이다.
동시성 컬렉션의 스레드-안전성을 파괴하는 것은 불가능하기 때문에 안전하게 사용할 수 있다.
자체적으로 동시성이 해결되어 있기 때문에 외부에서 lock을 사용하면 속도가 느려진다.
동시성을 무력화하는 것이 불가능하기 때문에 여러 메서드를 원자적으로 묶어 호출하는 것이 불가능하고, 그렇기 때문에 여러 기본 동작을 하나의 원자적 동작으로 묶는 상태 의존적 수정 메서드들이 추가되었다.
private static final ConcurrentMap<String, String> map = new ConcurrentHashMap<>(); public static String intern(String s) { String result = map.get(s); if (result == null) { result = map.putIfAbsent(s, s); if (result == null) result = s; } return result; }
Java
복사
이러한 concurrent 패키지의 동시성 컬렉션들은 동시성이 뛰어나고 속도도 무척 빠르니, 기존의 동기화된 컬렉션은 사용하지말자.(Colletions.synchronizedMap보다 ConcurrentHashMap을 사용하자)

동기화 장치

동기화 장치는 스레드가 다른 스레드를 기다릴 수 있게 하여, 서로 작업을 조율할 수 있게 해준다.
CountDownLatch와 Semaphor, Phaser, CyclicBarrier, Exchanger 등이 있다.
CountDownLatch는 하나 이상의 스레드가 또다른 하나 이상의 스레드 작업이 끝날 때까지 기다리게 한다. 매개변수로 int 값을 받아 countDown 메서드를 몇 번 호출해야 대기 중인 스레드들을 깨울지 결정한다.
public static long time(Executor executor, int concurrency, Runnable action) throws InterruptedException{ CountDownLatch ready = new CountDownLatch(concurrency); CountDownLatch start = new CountDownLatch(1); CountDownLatch done = new CountDownLatch(concurrency); for(int i = 0; i < concurrency; i++) { executor.execute(() -> { //타이머에게 준비를 마쳤음을 알린다. ready.countDown(); try { //모든 작업자 스레드가 준비될 때까지 기다린다 start.await(); action.run(); } catch (InterruptedExceptione) { Thread.currentThread().interrupt(); }finally{ //타이머에게 작업을 마쳤음을 알린다. done.countDown() ; } }); } ready.await(); // 모든 작업자가 준비될 때까지 기다린다. long startNanos = System.nanoTime(); start.countDown(); // 작업자들을 깨운다. done.await(); // 모든 작업자가 일을 끝마치기를 기다린다. return System.nanoTime() - startNanos; }
Java
복사
위 예시는 어떤 동작들을 동시에 시작해 모두 완료하기 까지의 시간을 측정하는 코드로, 세 개의 카운트다운 래치를 사용하여 시간을 측정한다.
위의 time 메서드에 concurrency로 넘겨주는 매개변수만큼 스레드를 생성할 수 없다면, 해당 메서드는 스레드 기아 교착상태(thread starvation deadlock)가 발생한다.