brunch

You can make anything
by writing

C.S.Lewis

by 이승현 Oct 01. 2018

동시성(Concurrency)

#67 지나친 동기화는 피하자

Effective Java - 동시성(Concurrency)


#67 지나친 동기화는 피하자


지나친 동기화는 성능을 저하시키고, 교착 상태에 빠지거나 예기치 못한 상황을 발생시킬 수 있습니다.


당연한 얘기인데 지나친 동기화를 피하는 방법에 대해 알아보겠습니다.




#01 예외 (Exception)


아래 코드는 Set에 요소들(E)이 추가될 때마다 클라이언트가 알 수 있도록, 관찰자(Observer) 패턴을 적용했습니다.

#01 ObservableSet class


SetObserver라는 타입을 가지는 인스턴스를 addObserver 메서드를 통해 전달합니다.

#02 SetObserver interface


아래와 같이 코드를 실행하면, 아마도 관찰자에 의해 넘어온 값이 4가 되는 순간 removeObserver 메서드를 통해 자신(this)을 삭제할 것입니다.

따라서 0부터 4까지 출력하고 끝날 것 같지만, 실제로는 0부터 4까지 출력 후 ConcurrenctModificationException 예외가 발생됩니다.

#03 main method


notifyElementAdded 메서드를 보면 synchronized 키워드를 통해 observers 객체를 동기화를 하고 있는데, 이 과정에서 removeObserver(this)를 통해 observers 객체를 변경하려고 하고 있습니다.

#04 ConcurrenctModificationException exception


이렇듯 동기화된 영역에서 처리 중에, 다른 동기화 처리를 요청이 들어오면 예외가 발생할 수 있습니다.

#05 exception




#02 교착 상태(deadlock)


아래는 직접 removeObserver 메서드를 통해 자신(this)을 삭제하지 않고, ExecutorService를 통해 다른 스레드에서 삭제하는 코드입니다.

#06 Executor service


이미 메인 스레드가 lock을 갖고 있는 상황에서, 백그라운드 스레드가 lock을 요청하고 있습니다.

백그라운드 스레드에서는 메인 스레드의 lock을 획득하기 위해 기다려야 하는데, 메인 스레드에서는 반대로 백그라운드 스레드가 observer의 삭제 완료를 기다리고 있습니다.

즉, 교착 상태(dead lock)에 걸려버립니다. 

#07 deadlock




외계인 메서드(문제가 되는 메서드) 외부로 이동


앞서 발생하는 예외나 교착 상태를 해결하기 위해서는 문제가 되는 메서드 호출을 동기화 블록 밖으로 옮기면 됩니다.

observer.add() 메서드가 문제가 되는 메서드 이기 때문에 이를 동기화 블록 밖으로 옮깁니다.

이를 개방 호출(open call)이라 부릅니다.

개방 호출을 통해 예외를 방지하고 동시성 성능도 향상할 수 있습니다.


#08 fix


이외에도 List 자체를 CopyOnWriteArrayList로 선언해서 해결할 수 있습니다.
CopyOnWriteArrayList는 내부적으로 복사본을 통해 로직을 처리하기 때문에 동기화 문제가 발생하지 않습니다.




동기화로 인해 문제가 되는 메서드는 절대 내부에서 호출하면 안 됩니다.

그리고 그 더 나은 성능을 위해서는 동기화된 블록 안에서의 처리는 최소한으로 해야 합니다.

만약 동기화가 꼭 필요한 상황이라면, 이에 대한 내용을 명확하게 문서화해야 합니다.

매거진의 이전글 동시성(Concurrency)
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari