본문 바로가기
JAVA/Effective Java

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

by Garonguri 2022. 8. 31.
728x90

 

새로운 사실!

wait 그리고 notify는 올바르게 사용하기가 아주 까다롭다. 따라서 고수준 동시성 유틸리티를 사용하는 것이 좋다.

 

wait와 notify란?

: 스레드의 상태 제어를 위한 메소드

 

wait : 가지고 있던 고유 락을 해제하고, 스레드를 잠들게 하는 역할을 하는 메서드

notify : 잠들어 있던 스레드 중 임의로 하나를 골라 깨우는 역할을 하는 메서드

 


java.util.concurrent의 고수준 유틸리티

  1. 실행자 프레임워크
  2. 동시성 컬렉션
    • List, Queue, Map과 같은 표준 컬렉션 인터페이스에 동시성을 가미해 구현한 고성능 컬렉션
    • 높은 동시성에 도달하기 위해 동기화를 각자 내부에서 수행
    • 따라서 동시성 컬렉션에서 동시성을 무력화하는 것은 불가능하다. 외부에서 락을 추가로 사용하면 오히려 속도가 느려진다.
      • 이 때문에 여러 기본 동작을 하나의 원자적 동작으로 묶는 '상태 의존적 수정'메서드가 추가됨.
    • 동기화한 컬렉션을 낡은 유산으로 만들어버린다.
      • 동기화한 컬렉션인 Collections.synchronizedMap 보다는, 동시성 컬렉션인 ConcurrentHashMap을 사용하는게 훨씬 좋다.
      • 극적인 성능 개선!
  3. 동기화 장치
    • 스레드가 다른 스레드를 기다릴 수 있게 하여, 서로 작업을 조율할 수 있게 해준다.
    • Phaser > CountDownLatch, Semaphore > CyclicBarrier, Exchanger 순으로 자주 쓰인다.
    • CountDownLatch
      • 일회성 장벽
      • 하나 이상의 스레드가 또 다른 하나 이상의 스레드 작업이 끝날 때까지 기다리게 한다.
      • int값을 받는데, 이 값을 통해 countDown을 몇 번 호출해야 대기중인 스레드를 깨울지 결정한다.

새로운 코드를 작성할 때는 wait, notify보다는 동시성 유틸리티를 사용하라는 것이 item 81의 내용이지만,

어쩔 수 없이 wait과 notify를 사용해야 하는 경우가 있다.

 

wait 메소드의 경우,

  • 스레드가 어떤 조건이 충족되기를 기다리게 할 때 사용한다.
  • 락 객체의 wait 메서드는 반드시 그 객체를 잠근 동기화 영역 안에서 호출해야 한다.
  • 반드시 대기 반복문(wait loop) 관용구를 사용해야 한다.
  • 반복문 밖에서는 절대로 호출하면 안된다.

대기한 이후에 조건을 검사하여 조건을 충족하지 않았을 때 다시 대기하게 하는 경우도 있다.

이는 조건이 만족되지 않아도 스레드가 깨어날 수 있는 상황이 몇 가지 있기 때문이다. (안전 실패 예방)

  • notify를 호출하여 대기 중인 스레드가 깨어나는 사이에 다른 스레드가 락을 거는 경우
  • 조건이 만족되지 않았지만 실수 혹은 악의적으로 notify를 호출하는 경우
  • 대기 중인 스레드 중 일부만 조건을 충족해도 notifyAll로 모든 스레드를 깨우는 경우
  • 대기 중인 스레드가 드물게 notify 없이 깨어나는 경우 (허위 각성)

 

notify 메소드의 경우,

  • 일반적으로 notify보다 notifyAll을 사용하는 것이 합리적이고 안전하다.
  • 모든 스레드가 같은 조건을 기다리고, 조건이 한 번 충족될 때마다 단 하나의 스레드만 혜택을 받을 수 있다면 notify를 사용하는 것이 최적화를 하는 방법이다.
  • 그러나... 위 조건을 만족하더라도 notifyAll을 사용하는 것이 나을 수 있다.
    • 관련 없는 스레드가 실수로 or 악의적으로 wait를 호출하는 공격으로부터 보호할 수 있기 때문이다.

 

핵심 정리

wait와 notify를 직접 사용하는 것은 어셈블리어로 프로그래밍 하는 것과 같다.
java.util.concurrent는 고수준 언어에 비유할 수 있다.
새롭게 코드를 작성할 때는 wait과 notify를 사용하지 말아라.
만약 레거시 코드에서 이를 어쩔 수 없이 사용해야 한다면, wait는 항상 while문 안에서 호출해라.
또한, 일반적으로 notify보다는 notifyAll을 사용해라.
혹시라도 notify를 사용한다면, 응답 불가 상태에 빠지지 않도록 각별히 주의하자.

 

728x90

댓글