Item 79. Excessive Synchronization

κ³Όλ„ν•œ λ™κΈ°ν™”λŠ” ν”Όν•˜λΌ

λ™κΈ°ν™”λŠ” μŠ€λ ˆλ“œ μ•ˆμ „μ„±μ„ 보μž₯ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λŠ”λ°, κ³Όλ„ν•œ λ™κΈ°ν™”λŠ” μ„±λŠ₯을 μ €ν•˜μ‹œν‚€κ±°λ‚˜, λ°λ“œλ½μ„ λ°œμƒμ‹œν‚€κ³ , 더 큰 문제λ₯Ό λ°œμƒμ‹œν‚¬ 수 μžˆλ‹€. μ΄λŸ¬ν•œ 문제λ₯Ό ν”Όν•˜κΈ° μœ„ν•΄μ„ , 동기화 λ©”μ„œλ“œλ‚˜ 동기화 블둝 μ•ˆμ˜ μ œμ–΄λ₯Ό ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ λ„˜κΈ°λŠ” μ•„λž˜μ™€ 같은 μ‚¬μš©μ€ ν”Όν•΄μ•Ό ν•œλ‹€.

  • λ™κΈ°ν™”λœ μ˜μ—­ μ•ˆμ—μ„œ μž¬μ •μ˜ν•  수 μžˆλŠ” λ©”μ„œλ“œ 호좜

  • ν΄λΌμ΄μ–ΈνŠΈκ°€ λ„˜κ²¨μ€€ ν•¨μˆ˜ 객체 호좜

μ™ΈλΆ€μ—μ„œ μž”λ‹¬ 받은 μ½”λ“œλŠ” 동기화 된 μ˜μ—­ λ‚΄μ—μ„œ μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚€κ±°λ‚˜, λ°λ“œλ½μ— λΉ μ§€κ±°λ‚˜ 데이터λ₯Ό μ†μƒμ‹œν‚¬ 수 μžˆλ‹€.

μ˜ˆμ™Έκ°€ λ°œμƒν•˜λŠ” μ˜ˆμ‹œ

μ•„λž˜λŠ” 집합(Set)을 감싼 래퍼 클래슀둜, 집합에 μ›μ†Œκ°€ μΆ”κ°€λ˜λ©΄ μ•Œλ¦Όμ„ 받을 수 μžˆλŠ” κ΄€μ°°μž νŒ¨ν„΄μ„ κ΅¬ν˜„ν•œ μ½”λ“œμ΄λ‹€.

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

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

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

    // ꡬ독 μΆ”κ°€
    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);
        }
        return result;
    }
}

public class Test {

    public static void main(String[] args) {
        ObservableSet<Integer> set = new ObservableSet<>(new HashSet<>());

        // κ΄€μ°°μž μΆ”κ°€
        set.addObserver(new SetObserver<>() {
            @Override
            public void added(ObservableSet<Integer> set, Integer element) {
                System.out.println(element);
                // νŠΉμ • μ›μ†Œ(=23)이 μΆ”κ°€λ˜λ©΄ 자기 μžμ‹ (κ΄€μ°°μž)을 제거
                if (element == 23) {
                    set.removeObserver(this);
                }
            }
        });

        for (int i = 0; i < 100; i++) {
            set.add(i);
        }
    }
}

μœ„ μ½”λ“œλŠ” SetObserverλ₯Ό κ΅¬ν˜„ν•œ κ΄€μ°°μžλ₯Ό μΆ”κ°€ν•˜κ³ , μ›μ†Œκ°€ 좔가될 λ•Œλ§ˆλ‹€ κ΄€μ°°μžμ—κ²Œ μ•Œλ¦Όμ„ λ³΄λ‚΄λŠ” ObservableSet ν΄λž˜μŠ€μ΄λ‹€. μ—¬κΈ°μ„œ νŠΉμ • μ›μ†Œμ—μ„œ κ΄€μ°°μž 슀슀둜λ₯Ό μ œκ±°ν•˜λŠ” μ½”λ“œλ₯Ό μΆ”κ°€ν–ˆλŠ”λ°, 이 μ½”λ“œμ—μ„œ ConcurrentModificationException μ—λŸ¬κ°€ λ°œμƒν•œλ‹€.

  1. set.add(i) λ©”μ„œλ“œ λ‚΄λΆ€μ—μ„œ notifyElementAddedλ₯Ό ν˜ΈμΆœν•˜κ³ , ν•΄λ‹Ή λ©”μ„œλ“œ λ‚΄λΆ€μ—μ„œ added λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜κ²Œ 됨

  2. 즉, added λ©”μ„œλ“œλŠ” notifyElementAddedμ—μ„œ 리슀트λ₯Ό μˆœνšŒν•˜λŠ” 도쀑에 μˆ˜ν–‰λ¨

  3. κ΅¬ν˜„ν•œ added λ©”μ„œλ“œ λ‚΄λΆ€μ—μ„œ removeObserverλ₯Ό ν˜ΈμΆœν•˜μ—¬ 리슀트λ₯Ό μˆ˜μ •ν•˜λ € μ‹œλ„

  4. ν•˜μ§€λ§Œ notifyElementAdded λ©”μ„œλ“œ λ‚΄λΆ€μ—μ„œ 리슀트 순회 도쀑 리슀트λ₯Ό μˆ˜μ •ν•˜λ € μ‹œλ„ν–ˆκΈ° λ•Œλ¬Έμ— μ—λŸ¬ λ°œμƒ

μœ„ μ½”λ“œμ—μ„  notifyElementAdded 동기화 블둝 μ•ˆμ— λ‚΄λΆ€μ—μ„œ μˆœνšŒν•˜λŠ” μ½”λ“œκ°€ μžˆμ–΄ λ™μ‹œ μˆ˜μ •μ΄ μΌμ–΄λ‚˜μ§€ μ•Šλ„λ‘ 보μž₯ν•˜μ§€λ§Œ, μ½œλ°±μ„ 거쳐 μˆ˜μ •ν•˜λŠ” 뢀뢄은 동기화 블둝 λ°– μ˜μ—­μ΄κΈ° λ•Œλ¬Έμ— 막지 λͺ»ν•œλ‹€.

λ°λ“œλ½μ΄ λ°œμƒν•˜λŠ” μ˜ˆμ‹œ

λ‹€μŒμ—” ν…ŒμŠ€νŠΈ μˆ˜ν–‰ 뢀뢄을 μˆ˜μ •ν•˜μ—¬, λ‹€λ₯Έ μŠ€λ ˆλ“œμ—μ„œ κ΄€μ°°μžλ₯Ό μ œκ±°ν•˜λ„λ‘ μˆ˜μ •ν•΄λ³΄μž.

public class Test {

    public static void main(String[] args) {
        ObservableSet<Integer> set = new ObservableSet<>(new HashSet<>());

        // κ΄€μ°°μž μΆ”κ°€
        set.addObserver(new SetObserver<>() {
            @Override
            public void added(ObservableSet<Integer> set, Integer element) {
                System.out.println(element);

                if (element == 23) {
                    // removeObserverλ₯Ό 직접 ν˜ΈμΆœν•˜μ§€ μ•Šκ³  λ‹€λ₯Έ μŠ€λ ˆλ“œμ—μ„œ μˆ˜ν–‰
                    ExecutorService exec = Executors.newSingleThreadExecutor();
                    try {
                        exec.submit(() -> set.removeObserver(this)).get();
                    } catch (InterruptedException | ExecutionException e) {
                        throw new AssertionError(e);
                    } finally {
                        exec.shutdown();
                    }
                }
            }
        });

        for (int i = 0; i < 100; i++) {
            set.add(i);
        }
    }
}

이번 μ½”λ“œμ—μ„  μ˜ˆμ™Έκ°€ λ°œμƒν•˜μ§„ μ•Šμ§€λ§Œ λ°λ“œλ½μ΄ λ°œμƒν•˜μ—¬ 계속 λŒ€κΈ°ν•˜λŠ” μƒνƒœκ°€ λœλ‹€.

  1. λ‹€λ₯Έ μŠ€λ ˆλ“œμ—μ„œ set.removeObserver(this)λ₯Ό ν˜ΈμΆœν•˜λ©΄μ„œ μž κΈˆμ„ νšλ“ν•˜λ € μ‹œλ„

  2. ν•˜μ§€λ§Œ 이미 순회 쀑인 메인 μŠ€λ ˆλ“œκ°€ notifyElementAdded λ©”μ„œλ“œ λ‚΄λΆ€μ—μ„œ μž κΈˆμ„ νšλ“ν•˜κ³  μžˆμ–΄ λŒ€κΈ°

  3. λ˜ν•œ 메인 μŠ€λ ˆλ“œλŠ” λ‹€λ₯Έ μŠ€λ ˆλ“œμ—μ„œ set.removeObserver(this)λ₯Ό λŒ€κΈ°ν•˜κ³  μžˆμ–΄ λ°λ“œλ½ λ°œμƒ

ν•΄κ²° 방법

μ΄λŸ¬ν•œ 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„ , λ™κΈ°ν™”λœ μ˜μ—­ μ•ˆμ—μ„œ ν΄λΌμ΄μ–ΈνŠΈκ°€ λ„˜κ²¨μ€€ ν•¨μˆ˜ 객체 ν˜ΈμΆœμ„ ν”Όν•˜λ©΄ λœλ‹€.

private void notifyElementAdded(E element) {
    List<SetObserver<E>> snapshot = null;
    synchronized (observers) {
        snapshot = new ArrayList<>(observers);
    }
    for (SetObserver<E> observer : snapshot) {
        // 동기화 블둝 λ°–μ—μ„œ μ™ΈλΆ€ μ •μ˜ 된 `added` λ©”μ„œλ“œ 호좜 
        observer.added(this, element);
    }
}

μ΄λ ‡κ²Œ 직접 λ‘œμ§μ„ 일뢀 μˆ˜μ •ν•˜μ—¬ ν•΄κ²°ν•˜λŠ” 방법도 μ‘΄μž¬ν•˜μ§€λ§Œ, μžλ°”μ—μ„œ μ œκ³΅ν•˜λŠ” λ™μ‹œμ„± μ»¬λ ‰μ…˜μ„ μ‚¬μš©ν•˜λŠ” 방법도 μžˆλ‹€.

private final Set<SetObserver<E>> observers = new CopyOnWriteArraySet<>();

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);
    }
//    }
}

ArrayList λŒ€μ‹  CopyOnWriteArraySet둜 μˆ˜μ •ν•˜κΈ°λ§Œ ν•˜λ©΄ κΈ°μ‘΄ μ½”λ“œμ—μ„œλ„ λ™μ‹œ μˆ˜μ •μ΄ μΌμ–΄λ‚˜λ„ μ•ˆμ „ν•˜κ²Œ λ™μž‘ν•œλ‹€. λ˜ν•œ μ»¬λ ‰μ…˜ λ‚΄λΆ€μ—μ„œ 동기화λ₯Ό μ²˜λ¦¬ν•˜κΈ° λ•Œλ¬Έμ— λͺ…μ‹μ μœΌλ‘œ μΆ”κ°€ν•œ 동기화 블둝은 μ œκ±°ν•΄λ„ λœλ‹€.

κ²°λ‘ 

λ‹¨μˆœνžˆ μ˜ˆμ™Έ λ°œμƒμ΄λ‚˜ λ°λ“œλ½μ„ ν”Όν•˜λŠ” 것도 μ€‘μš”ν•˜μ§€λ§Œ, λ©€ν‹° μŠ€λ ˆλ“œ 처리의 λͺ©μ μ€ μ„±λŠ₯ ν–₯상이닀. λ©€ν‹° μŠ€λ ˆλ“œ μˆ˜ν–‰ 쀑 κ°€μž₯ λ§Žμ€ λΉ„μš©μ΄ λ°œμƒν•˜λŠ” 뢀뢄은 지연 μ‹œκ°„μœΌλ‘œ, 지연 μ‹œκ°„μ„ μ€„μ΄λŠ” 것은 곧 μ„±λŠ₯ ν–₯μƒμœΌλ‘œ 이어진닀. 동기화λ₯Ό 효과적으둜, 그리고 μ•ˆμ •μ μœΌλ‘œ μ‚¬μš©ν•˜κΈ° μœ„ν•΄μ„  μ•„λž˜ κ·œμΉ™μ„ λ”°λ₯΄μž.

  • μž¬μ •μ˜ν•  수 μžˆκ±°λ‚˜ μ™ΈλΆ€μ—μ„œ λ„˜μ–΄μ˜¨ λ©”μ„œλ“œ 동기화 μ˜μ—­ λ‚΄μ—μ„œ μˆ˜ν–‰ κΈˆμ§€

  • 동기화 μ˜μ—­ λ‚΄μ—μ„œ μˆ˜ν–‰ν•˜λŠ” 일을 μ κ²Œν•˜μ—¬ 락을 가지고 μžˆλŠ” μ‹œκ°„μ„ μ΅œμ†Œν™”

Last updated

Was this helpful?