81. wait와 notify보다는 동시성 유틸리티를 애용하라
📌 1. 발표 전 알아야 할 개념
동시성(Concurrency)
여러 작업이 논리적으로 동시에 실행되는 것
멀티 스레딩, 병렬 처리 등을 통해 작업 효율성 향상
시스템 자원 활용 최적화, 응답성 향상, 처리량 증가 등이 목적
다시 한 번 복습
스레드(Thread) : 프로세스 내 실행되는 흐름의 단위
공유 자원 : 여러 스레드가 동시에 접근할 수 있는 데이터나 리소스
동기화(Synchronization) : 여러 스레드가 공유 자원에 안전하게 접근하게 하는 매커니즘
데드락(Deadlock) : 두 개 이상의 스레드가 서로 자원을 기다리며 무한히 대기하는 상태
📕 2. wait와 notify
// 자바의 동시성 매커니즘 발전 과정
초기 자바 → wait()/notify() → java.util.concurrent 패키지
2-1. wait()
와 notify()
메서드
wait()
와 notify()
메서드동시성 프로그래밍의 기본이 되는 메서드들
wait()
: 스레드가 특정 조건이 충족될 때까지 대기하게 함notify()
: 대기 중인 스레드 중 하나를 깨움notifyAll()
: 대기 중인 모든 스레드를 깨움
synchronized (obj) {
while (<조건이 충족되지 않았다>)
obj.wait(); // 락을 놓고, 깨어나면 다시 잡는다
// 조건이 충족됐을 때의 동작을 수행한다
}
반드시 synchronized 블록 안에서 호출해야 한다
객체의 내장 모니터 락과 연계하여 동작한다
2-2. wait()
과 notify()
의 한계점
wait()
과 notify()
의 한계점정확한 락 제어가 필요하다
반드시 synchronized 블록 안에서만 사용 가능함
오류 발생 가능성이 높다
spurious wakeup
: 조건이 만족되지 않았는데, 스레드가 깨어나는 경우가 존재함
정교한 동기화 패턴 구현이 어렵다
notify()
는 무작위 하나의 스레드만 깨움 - 필요한 스레드가 아닐 수 있음notifyAll()
은 모든 대기 스레드를 깨우지만 성능 저하의 우려가 있음
🔧 3. java.util.concurrent
로 대체하자
java.util.concurrent
로 대체하자자바의 고수준 동시성 유틸리티를 제공하는 패키지
wait()/notify()의 단점을 보완하기 위해 도입
3-1. ConcurrentMap
- 스레드 안전한 Map
ConcurrentMap
- 스레드 안전한 Map동시에 여러 스레드가 값을 넣거나 꺼내도, 충돌 없이 안전하게 동작함
// 문자열 중복 제거 기능
private static final ConcurrentMap<String, String> map = new ConcurrentHashMap<>();
public static String intern(String s) {
String result = map.putIfAbsent(s, s); // key가 없을 때만 추가
return result == null ? s : result;
}
putIfAbsent
는 동시에 여러 스레드가 같은 키로 put해도, 단 하나만 저장되도록 보장synchronized
없이도 안전하며, 코드가 간결해지고 성능도 향상됨
3-2. CountDonwLatch
- 스레드 실행 및 타이밍 제어
CountDonwLatch
- 스레드 실행 및 타이밍 제어모든 스레드가 준비되면, 하나의 신호(countDown)로 동시에 출발함
N개의 작업을 동시에 시작, 모두 끝날때까지 대기
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(); // 준비 완료
start.await(); // 시작 신호 기다림
action.run(); // 작업 실행
done.countDown(); // 작업 완료
});
}
ready.await(); // ready : 모든 스레드 준비될 때까지 대기
long startTime = System.nanoTime();
start.countDown(); // start : 일제히 시작!
done.await(); // done : 모든 스레드 작업 끝날 때까지 대기
return System.nanoTime() - startTime;
}
ready
: 모든 작업자 준비 신호start
: 한 번에 모두 시작(출발 신호)done
: 작업 종료 확인
3-3. BlockingQueue
- 생산자-소비자 문제의 해답
BlockingQueue
- 생산자-소비자 문제의 해답생산자(Producer)가 데이터를 만들고, 소비자(Consumer)가 소비하는 구조
큐가 꽉 차면 기다리고, 비면 또 기다리는 자동 신호 시스템
3-4. java.util.concurrent.atomic
패키지
java.util.concurrent.atomic
패키지락 없이도 원자적 연산을 지원하는 클래스들
AtomicInteger
,AtomicLong
,AtomicReference
등
// 스레드 안전한 카운터
private static final AtomicInteger counter = new AtomicInteger();
public static int incrementAndGet() {
return counter.incrementAndGet(); // 원자적 증가 연산
}
👍🏼 향후 발전 포인트
또 다른 유용한 동시성 유틸리티들
Semapore
- 리소스 접근 제한CyclicBarrier
- 여러 스레드가 특정 지점에서 만남Pharser
- 더 유연한 단계별 동기화화
🤖 최종 결론
wait, notify는 저수준 매커니즘으로 오류가능성이 높다 java.util.concurrent 는 더 높은 수준의 유틸리티를 제공한다 wait / notify 대신 동시성 유틸리티를 사용하자
😶🌫️ 느낀점
자바의 유틸리티 메서드를 공부하자
Last updated