본문 바로가기
프로그래밍 놀이터/디자인 패턴, 리펙토링

[Effective Java] wait 와 notify 대신 동시성 유틸리티를 사용하자.

by 돼지왕 왕돼지 2017. 3. 13.
반응형

 [Effective Java] wait 와 notify 대신 동시성 유틸리티를 사용하자.


blockiing operation, BlockingQueue, collections.synchronizedmap, concurrent, ConcurrentHashMap, countdownlatch, currentTimeMillis, cyclicbarrier, Effective JAVA, exchanger, executor framework, Hashtable, java.util.concurrent, list, map, Notify, notifyAll, putifabsent, Queue, semaphore, spurious wakeup, state-dependent modify operation, synchronized, synchronizer, System, system.currenttimemillis, system.nanotime, Wait, wait notify, While, [Effective Java] wait 와 notify 대신 동시성 유틸리티를 사용하자., 강추, 고성능, 고수준, 고수준 동시성 유틸리티, 구식 동기화 map, 느리게, 다른 스레드, 대기, 대기 상태 스레드, 대기 전, 대기 후, 대기 후 조건 검사, 동기자, 동시성, 동시성 관련 활동 제외, 동시성 어셈블리 언어, 동시성 유틸리티, 동시적 map, 동시적 구현체, 동시적 컬렉션, 동시적 컬렉션 및 동기자, 락, 루프, 리얼타임 클럭, 블록 연산, 비논리 기상, 상태 종속 변경 연산, 성능, 세 부류, 스레드, 스레드 활동성, 실행자 프레임워크, 안전, 영향, 외부적으로 동기화되는 컬렉션, 유틸리티, 이디엄, 정밀, 정확, 처리 속도, 컬렉션 인터페이스, 패키지, 표준 이디엄, 표준 컬렉션 인터페이스, 프로그램, 향상, 활동성


-
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 를 사용한다면, 스레드의 활동성이 보장되도록 주의해야 한다.





반응형

댓글