[Effective Java] wait 와 notify 대신 동시성 유틸리티를 사용하자. |
-
wait 와 notify 를 사용할 이유가 거의 없다.
자바 1.5 배포판 기준으로 고수준 동시성 유틸리티를 제공한다.
wait와 notify 를 올바르게 사용하기 어렵다면, 그 대신에 고수준 동시성 유틸리티를 사용해야 한다.
-
java.util.concurrent 패키지의 고수준 유틸리티는 세 부류로 나누어진다.
실행자 프레임워크(executor framework)
동시적 컬렉션 및 동기자(synchronizer)
-
동시적 컬렉션은 List, Queue, Map 과 같은 표준 컬렉션 인터페이스를 고성능의 동시적 구현체로 제공한다.
높은 동시성을 제공하기 위해 이 구현체들은 내부적으로 자기 나름의 동기화를 한다.
동시적 컬렉션으로부터 동시성 관련 활동을 제외하는 것은 불가능하므로, 그런 컬렉션이 갖게 될 락은 효과가 없고 프로그램만 느리게 할 뿐이다.
그래서 상태 종속 변경 연산(state-dependent modify operation)으로 확장되었다.
예를 들면 putIfAbsent 와 같은 함수들이다.
-
뛰어난 동시성을 제공하는 것 외에도 ConcurrentHashMap 은 처리 속도가 매우 빠르다.
특별한 이유가 없는 한, Collections.synchronizedMap 이나 HashTable 대신 ConcurrentHashMap 을 사용하자.
구식의 동기화 Map 을 동시적 Map 으로 바꾸면, 동시적 애플리케이션의 성능이 놀랄 만큼 향상된다.
더 일반적으로는 외부적으로 동기화되는 컬렉션보다 동시적 컬렉션을 사용하자.
-
컬렉션 인터페이스 중 일부는 블록 연산(blocking operation) 으로 확장되었다.
BlockingQueue 가 대표적인 예이다.
-
동기자(synchronizer) 는 스레드가 다른 스레드를 대기시킬 수 있게 해주는 객체로, 스레드 간의 활동을 조정할 수 있게 해준다.
가장 많이 사용하는 동기자 클래스는 CountDownLatch 와 Semaphore 이며, 가장 적게 사용되는 것은 CyclicBarrier 와 Exchanger 이다.
-
CountdownLatch 는 하나 이상의 스레드가 하나 이상의 다른 스레드를 대기시킬 수 있게 해준다.
CountDownLatch 클래스의 생성자에서는 대기 중인 모든 스레드가 작업을 진행할 수 있기 전까지 몇 번의 countDown 메소드를 호출해야 하는지
int 로 전달받는다.
-
cf) 시간 간격을 잴 때는 System.currentTimeMillis 대신에 항상 System.nanoTime 을 사용하자.
System.nanoTime 이 더 정확하고 정밀하며, 시스템의 리얼타임 클럭을 조정해도 영향을 받지 않기 떄문이다.
-
wait 메소드의 사용을 위한 표준 이디엄(idiom)은 다음과 같다.
synchronized( obj ){
while( <대기 상태 벗어날 조건 만족하지 않으면> ){
obj.wait();
}
}
항상 wait 루프 이디엄을 사용해서 wait 메소드를 호출하자.
절대로 루프 밖에서 호출하지 않는다.
루프는 대기 전과 후의 조건을 검사하는 데 사용한다.
대기 후의 조건 검사와 다시 대기하는 것(조건 불만족시)은 안전을 확보하기 위해 필요하다.
!!! 대기 상태인 스레드가 notify 없이 저절로 깨어날 수 있다. ( 이런 일은 거의 없지만 ).
이것을 비논리 기상 ( spurious wakeup ) 이라고 한다 !!!
따라서 while 문을 쓰는 것이 강추된다.
Summary
wait 와 notify 를 직접 사용하는 것은, 마치 "동시성 어셈블리 언어"로 프로그래밍 하는 것과 같다.
새로 작성하는 코드에는 wait 와 notify 를 가급적 사용하지 말자.
만일 wait 와 notify 를 사용하는 코드를 유지보수 한다면, 표준 이디엄을 사용해서 항상 while 루프 안에서 wait 메소드를 호출하는지 확인하자.
일반적으로 notify 보다는 notifyAll 메소드를 사용해야 한다.
만일 notify 를 사용한다면, 스레드의 활동성이 보장되도록 주의해야 한다.
'프로그래밍 놀이터 > 디자인 패턴, 리펙토링' 카테고리의 다른 글
[Effective Java] 늦 초기화를 분별력 있게 사용하자 (0) | 2017.03.16 |
---|---|
[Effective Java] 스레드 안전을 문서화 하자. (0) | 2017.03.14 |
[Effective Java] 스레드 그룹보다는 실행자와 작업을 사용하자. (0) | 2017.03.10 |
[Effective Java] 지나친 동기화는 피하자 (0) | 2017.03.09 |
[Effective Java] 공유하는 가변 데이터에 접근 시 동기화하자. (0) | 2017.03.07 |
댓글