아이템 67. 과도한 동기화는 피하라.

67. 과도한 동기화는 피하라.

과도한 동기화 시 발생할 수 있는 문제

  1. 교착상태(deadlock)
  2. 비결정적 동작(nondeterministic behavior)

동기화 메서드나 블록 안에서 클라이언트에게 프로그램 제어 흐름을 넘기지 마라.

다시 말해서, 동기화가 적용된 영역(synchronized) 안에서는 재정의 가능 메서드나 클라이언트가 제공한 함수 객체 메서드를 호출하지 말라.

동기화 영역이 존재하는 관점에서 보면, 그런 메서드는 불가해(alien)메서드다. 무슨 일을 하는지 알수도 없고 제어할 수도 없다. 불가해 메서드가 어떤 일을 하냐에 따라, 동기화 영역 안에서 호출하게 되면 예외나 교착 상태, 데이터 훼손(data corruption)이 발생 할 수 있다.

불가해 메서드 오류 예제


public interface  SetObserver<E>{
    // 구독자 집합에 새 원소가 추가되었을 때 호출됨
    void added(ObservableSet<E> set, E element);
}

// 동기화 블록안에서 불가해 메서드를 호출하는 잘못된 사례!
public class ObservableSet<E> extends  ForwardingSet<E> {
    
    private final List<SetObserver<E>> observers = new ArrayList<SetObserver<E>>();
    
    public void addObserver(SetObserver<E> observer){
        synchronized (observers){
            observers.add(observer);
        }
    }
    
    private void notifyElementAdded(E element) {
        synchronized(observers) {
            for (SetObserver<E> observer : observers) {
                observer.added(this, element);
            }
        }
    }


    @Override 
    public boolean add(E element){
        boolean added = super.add(element);
        if(added)
            notifyElementAdded(element);
        return added;
    }
}
/**
 * 실제 동작시, 0 ~ 23까지 출력 후 ConcurrentModificationException 발생함
 */
public static void main(String[] args){
    ObservableSet<Integer> set = new ObservaleSet<Integer>(new HashSet<Integer>);
    
    set.addObserver(new SetObserver<Integer>(){
       public void added(ObservableSet<Integer> s, Integer e){
           System.out.println(e);
           if(e == 32)
               s.removeObserver(this);
       } 
    });
    
    for( int i = 0; i < 100; ++i ){
        set.add(i);
    }
}

위의 코드는 리스트를 순회 하고 있는 중(notifyElementAdded)에 원소를 삭제 하려 시도 하기 때문에 오류가 발생한다. notifyElementAdded 메서드의 순환문은 동기화 블럭 안에 있다. observers리스트가 병렬적으로 수정되는 일을 막기 위해서였다. 하지만, 이렇게 했어도 순환문을 실행하는 스레드 자신이 구독자 집합에 저장된 메서드(```SetObserver.added``)를 역호출(callback)해서 observers리스트를 변경하는 경우까지 차단 할수는 없다.

불가해 메서드 오류 해결 방안

public void addObserver(SetObserver observer) { observers.add(observer); } public boolean removeObserver(SetObserver observer) { return observers.remove(observer); } private void notifyElementAdded(E element) { for (SetObserver observer : observers) { observer.added(this, element); } } ``` 이 리스트는 ```ArrayList```의 변종으로 내부 배열을 통째로 복사하는 방식으로 쓰기 연산을 지원한다.( 내부 배열을 절대로 수정하지 않음)

명심해야 할 것

결론