💡 과도한 동기화는 **성능을 떨어뜨리고**, **교착상태**에 빠뜨리고, 심지어 **예측할 수 없는 동작**을 낳기도 한다. 
💡 응답 불가와 안전 실패를 피하려면 동기화 메서드나 동기화 블록 안에서는 제어를 절대로 **클라이언트에 양도하면 안 된다**. 

예를 들어 동기화된 영역 안에서는 재정의할 수 있는 메서드는 호출하면 안 되며, 클라이언트가 넘겨준 함수 객체를 호출해서도 안 된다.

왜냐하면 그 메서드가 무슨 일을 할지 알지 못하며 통제할 수 없다는 뜻이다.

이는 예외를 발생 시키거나 교착상태에 빠지거나 데이터를 훼손할 수도 있다.

아래의 코드는 어떤 집합(Set)을 감싼 래퍼 클래스이다.

이 클래스의 클라이언트는 집합에 원소가 추가되면 알림을 받을 수 있다.

public class ObservableSet<E> extends ForwardingSet<E> {

    public ObservableSet(Set<E> set) {
        super(set);
    }

    private final List<SetObserver<E>> observers = new ArrayList<>();

    public void addObserver(SetObserver<E> observer) {
        synchronized (observers) {
            observers.add(observer);
        }
    }
    
    public boolean removeObserver(SetObserver<E> observer) {
        synchronized (observers) {
            return observers.remove(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;
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        boolean result = false;
        for (E element : c) {
            result |= add(element); //notifyElementAdded를 호출
        }
        return result;
    }
}

즉, 아래의 코드는 0부터 99까지 출력한다.