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?