Effective Java 3/E #1 - Item 78 ~ 79

mangooยท2022๋…„ 12์›” 8์ผ
0

๐Ÿ’ก ํ•ด๋‹น ํฌ์ŠคํŒ…์—์„œ ์†Œ๊ฐœ๋œ ์˜ˆ์ œ ์ฝ”๋“œ๋“ค์€ ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ฐœ์š”

์ดํŽ™ํ‹ฐ๋ธŒ ์ž๋ฐ” ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜๊ฒŒ ๋œ ๊ณ„๊ธฐ?

2๋…„ ์ „์— ์ดํŽ™ํ‹ฐ๋ธŒ ์ž๋ฐ” ์Šคํ„ฐ๋””๋ฅผ ํ–ˆ๊ณ  ์ด ํ™œ๋™์„ ํฌํŠธํด๋ฆฌ์˜ค์— ๋„ฃ์—ˆ๋‹ค. ๊ทธ๋žฌ๋”๋‹ˆ ํšŒ์‚ฌ ๊ทœ๋ชจ์™€ ์ƒ๊ด€์—†์ด ๋Œ€๋ถ€๋ถ„์˜ ์ธํ„ฐ๋ทฐ์—์„œ ์ดํŽ™ํ‹ฐ๋ธŒ ์ž๋ฐ”์—์„œ ์ธ์ƒ ๊นŠ์—ˆ๋˜ ๋‚ด์šฉ ํ˜น์€ ๊ธฐ์–ต ๋‚˜๋Š” ๋‚ด์šฉ์— ๋Œ€ํ•œ ์งˆ๋ฌธ์„ ๋ฐ›์•˜๋‹ค. ์˜ค๋ž˜ ์ „์— ํ–ˆ๋˜ ์Šคํ„ฐ๋””๋ผ ๊ธฐ์–ต ๋‚˜๋Š” ๋‚ด์šฉ์ด ๊ฑฐ์˜ ์—†์—ˆ๊ณ , ์ด๋ฅผ ๊ณ„๊ธฐ๋กœ ์ฑ… '์ดํŽ™ํ‹ฐ๋ธŒ ์ž๋ฐ”'๋ฅผ ๋‹ค์‹œ ํ•œ ๋ฒˆ ๊ณต๋ถ€ํ•  ํ•„์š”์„ฑ์ด ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

์ด์ „ ์Šคํ„ฐ๋””์—์„œ๋Š” ์•„์ดํ…œ 1๋ถ€ํ„ฐ ์ฐจ๋ก€๋กœ ๊ณต๋ถ€ํ–ˆ์ง€๋งŒ, ์ด๋ฒˆ์—๋Š” ๋‚ด๊ฐ€ ๊ณต๋ถ€ํ•˜๊ณ  ์žˆ๋Š” ์ฃผ์ œ์™€ ๊ด€๋ จ๋œ ์•„์ดํ…œ์„ ๊ณต๋ถ€ํ•˜๊ณ  ๋ธ”๋กœ๊ทธ ๊ธ€๋กœ ์ •๋ฆฌํ•˜๋ ค ํ•œ๋‹ค.

synchronized, volatile, atomic object์— ๋Œ€ํ•ด ๋ชจ๋ฅธ๋‹ค๋ฉด Java์—์„œ Thread-Safeํ•˜๊ฒŒ ๊ตฌํ˜„ํ•˜๊ธฐ ํฌ์ŠคํŒ…์„ ๋จผ์ € ์ฝ๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค.



Item 78: ๊ณต์œ  ์ค‘์ธ ๊ฐ€๋ณ€ ๋ฐ์ดํ„ฐ๋Š” ๋™๊ธฐํ™”ํ•ด ์‚ฌ์šฉํ•˜๋ผ

๋™๊ธฐํ™”๋Š” ๋ฐฐํƒ€์ ์ธ ์‹คํ–‰๊ณผ ์Šค๋ ˆ๋“œ ์‚ฌ์ด์˜ ์•ˆ์ •์ ์ธ ํ†ต์‹ ์— ๊ผญ ํ•„์š”ํ•˜๋‹ค.

๋ฐฐํƒ€์ ์ธ ์‹คํ–‰

๋ฐฐํƒ€์  ์‹คํ–‰์„ ํ†ตํ•ด ๊ฐ์ฒด๋ฅผ ํ•˜๋‚˜์˜ ์ผ๊ด€๋œ ์ƒํƒœ์—์„œ ๋‹ค๋ฅธ ์ผ๊ด€๋œ ์ƒํƒœ๋กœ ๋ณ€ํ™”์‹œํ‚จ๋‹ค. ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ณต์œ  ์ค‘์ธ ๊ฐ€๋ณ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์‚ฌ์ด์— ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ํ•œ๋‹ค.


์Šค๋ ˆ๋“œ ๊ฐ„ ํ†ต์‹ 

๋™๊ธฐํ™”๋œ ๋ฉ”์„œ๋“œ๋‚˜ ๋ธ”๋ก์— ๋“ค์–ด๊ฐ„ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐ™์€ ๋ฝ์˜ ๋ณดํ˜ธ ํ•˜์— ์ˆ˜ํ–‰๋œ ๋ชจ๋“  ์ด์ „ ์ˆ˜์ •์˜ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ฒŒ ํ•ด์ค€๋‹ค. ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ณต์œ  ์ค‘์ธ ๊ฐ€๋ณ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—๊ฒŒ ๋ณ€๊ฒฝ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณด์—ฌ์•ผ ํ•œ๋‹ค.

๋ฐฐํƒ€์ ์ธ ์‹คํ–‰์œผ๋กœ ์›์ž์„ฑ์„, ์Šค๋ ˆ๋“œ ๊ฐ„ ํ†ต์‹ ์œผ๋กœ ๊ฐ€์‹œ์„ฑ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ๋‹ค.


์˜ˆ์ œ #1

์ž๋ฐ” ์–ธ์–ด ๋ช…์„ธ์ƒ long๊ณผ double ์™ธ์˜ ๋ณ€์ˆ˜๋ฅผ ์ฝ๊ณ  ์“ฐ๋Š” ๋™์ž‘์€ ์›์ž์ ์ด๋‹ค[JLS 17.7]. ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐ™์€ ๋ณ€์ˆ˜๋ฅผ ๋™๊ธฐํ™” ์—†์ด ์ˆ˜์ •ํ•˜๋Š” ์ค‘์ด๋ผ๋„, ํ•ญ์ƒ ์–ด๋–ค ์Šค๋ ˆ๋“œ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ €์žฅํ•œ ๊ฐ’์„ ์˜จ์ „ํžˆ ์ฝ์–ด์˜ด์„ ๋ณด์žฅํ•œ๋‹ค๋Š” ๋œป์ด๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ์›์ž์  ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ  ์“ธ ๋•Œ ๋™๊ธฐํ™”๊ฐ€ ํ•„์š” ์—†์„๊นŒ? ์•„๋ž˜ ์ฝ”๋“œ์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ์˜ˆ์ธกํ•ด๋ณด์ž.

public class StopThread {

    private static boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(() -> {
            int i = 0;
            while (!stopRequested) {
                i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

ํ”„๋กœ๊ทธ๋žจ์€ while ๋ฌธ์„ ๋น ์ ธ๋‚˜์˜ค์ง€ ๋ชปํ•˜๊ณ  ๊ณ„์† ์ˆ˜ํ–‰๋œ๋‹ค. JLS 17.3์— ์ปดํŒŒ์ผ๋Ÿฌ๋Š” non-volatile boolean ํ•„๋“œ๋ฅผ ํ•œ ๋ฒˆ ์ฝ์–ด์˜จ ๋’ค ์บ์‹œํ•œ ๊ฐ’์„ ์žฌ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ํ•„๋“œ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋”๋ผ๋„ ๋ฐ˜๋ณต๋ฌธ์€ ์˜์›ํžˆ ์ข…๋ฃŒ๋˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ๋‚˜์™€ ์žˆ๋‹ค.

์ด์ฒ˜๋Ÿผ ๊ณต์œ  ์ค‘์ธ ๊ฐ€๋ณ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋น„๋ก ์›์ž์ ์œผ๋กœ ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ์„์ง€๋ผ๋„ ๋™๊ธฐํ™”์— ์‹คํŒจํ•˜๋ฉด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. ์•ž์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ ๋™๊ธฐํ™”์˜ ์—ญํ•  ์ค‘ ํ•˜๋‚˜๋Š” "์•ˆ์ •์ ์ธ ์Šค๋ ˆ๋“œ ๊ฐ„ ํ†ต์‹ "์ด๋‹ค. ๋™๊ธฐํ™”ํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ˆ˜์ •ํ•œ ๊ฐ’์„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ๊ฐ€ ์–ธ์ œ ๋ณด๊ฒŒ ๋ ์ง€ ์•Œ ์ˆ˜ ์—†๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ”„๋กœ๊ทธ๋žจ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์„๊นŒ?


์˜ˆ์ œ #1: synchronized

synchronized ํ•œ์ •์ž๋ฅผ ์‚ฌ์šฉํ•ด stopRequested ํ•„๋“œ๋ฅผ ๋™๊ธฐํ™”ํ•œ๋‹ค๋ฉด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

public class StopThread {

    private static boolean stopRequested;

    private static synchronized void requestStop() {
        stopRequested = true;
    }

    private static synchronized boolean stopRequested() {
        return stopRequested;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(() -> {
            int i = 0;
            while (!stopRequested()) {
                i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        requestStop();
    }
}

ํ”„๋กœ๊ทธ๋žจ์€ ์˜ˆ์ƒํ–ˆ๋˜ ๋Œ€๋กœ 1์ดˆ ํ›„ ์ข…๋ฃŒ๋œ๋‹ค. ์“ฐ๊ธฐ ๋ฉ”์„œ๋“œ(requestStop)์™€ ์ฝ๊ธฐ ๋ฉ”์„œ๋“œ(stopRequested)๋Š” ์›์ž์ ์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ์ฝ”๋“œ์—์„œ ๋™๊ธฐํ™”๋Š” ์Šค๋ ˆ๋“œ ๊ฐ„ ํ†ต์‹  ๋ชฉ์ ์œผ๋กœ๋งŒ ์‚ฌ์šฉ๋˜์—ˆ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.


์˜ˆ์ œ #1: volatile

stopRequested ํ•„๋“œ๋ฅผ volatile๋กœ ์„ ์–ธํ•ด ๋™๊ธฐํ™”๋ฅผ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ๋‹ค. volatile ํ•œ์ •์ž๋Š” ๋ฐฐํƒ€์  ์ˆ˜ํ–‰๊ณผ๋Š” ์ƒ๊ด€์—†์ง€๋งŒ ํ•ญ์ƒ ๊ฐ€์žฅ ์ตœ๊ทผ์— ๊ธฐ๋ก๋œ ๊ฐ’์„ ์ฝ๊ฒŒ ๋จ์„ ๋ณด์žฅํ•œ๋‹ค.

public class StopThread {

    private static volatile boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(() -> {
            int i = 0;
            while (!stopRequested) {
                i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

synchronized ์˜ˆ์ œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ”„๋กœ๊ทธ๋žจ์€ 1์ดˆ ํ›„ ์ข…๋ฃŒ๋œ๋‹ค.


์˜ˆ์ œ #2

volatile์€ ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์–ด๋–ค ๊ฐ’์ด ์ถœ๋ ฅ๋ ๊นŒ?

public class SerialNumber {

    private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(10);

    private static volatile int nextSerialNumber = 0;

    private static int generateNextSerialNumber() throws InterruptedException {
        Thread.sleep(1);
        return nextSerialNumber++;
    }

    public static void main(String[] args) throws InterruptedException {
        int N = 30;
        CountDownLatch latch = new CountDownLatch(N);

        for (int i = 0; i < N; i++) {
            THREAD_POOL.execute(() -> {
                try {
                    generateNextSerialNumber();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                latch.countDown();
            });
        }

        latch.await();
        System.out.println(nextSerialNumber);
    }
}

30์ด ์ถœ๋ ฅ๋˜์–ด์•ผ ํ•  ๊ฒƒ ๊ฐ™์ง€๋งŒ ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•ด๋ณด๋ฉด 30์— ๋ชป ๋ฏธ์น˜๋Š” ๊ฐ’์ด ์ถœ๋ ฅ๋œ๋‹ค. volatile ํ•œ์ •์ž๋Š” ํ•ญ์ƒ ๊ฐ€์žฅ ์ตœ๊ทผ์— ๊ธฐ๋ก๋œ ๊ฐ’์„ ์ฝ๊ฒŒ ๋จ์„ ๋ณด์žฅํ•˜๋Š”๋ฐ ์™œ ์ด๋Ÿฐ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜จ๊ฑธ๊นŒ?

๋ฌธ์ œ๋Š” ์ฆ๊ฐ€ ์—ฐ์‚ฐ์ž(++)๋‹ค. ์ด ์—ฐ์‚ฐ์ž๋Š” ์ฝ”๋“œ ์ƒ์œผ๋กœ๋Š” ํ•˜๋‚˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” nextSerialNumber ํ•„๋“œ์— ๋‘ ๋ฒˆ ์ ‘๊ทผํ•œ๋‹ค. ๋จผ์ € ๊ฐ’์„ ์ฝ๊ณ , ๊ทธ๋Ÿฐ ๋‹ค์Œ 1 ์ฆ๊ฐ€ํ•œ ์ƒˆ๋กœ์šด ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋งŒ์•ฝ ๋‘ ๋ฒˆ์งธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ด ๋‘ ์ ‘๊ทผ ์‚ฌ์ด๋ฅผ ๋น„์ง‘๊ณ  ๋“ค์–ด์™€ ๊ฐ’์„ ์ฝ์–ด๊ฐ€๋ฉด ์ฒซ ๋ฒˆ์งธ ์Šค๋ ˆ๋“œ์™€ ๋™์ผํ•œ ๊ฐ’์„ ๋Œ๋ ค๋ฐ›๊ฒŒ ๋œ๋‹ค.

IntelliJ์—์„œ๋„ ์•„๋ž˜์™€ ๊ฐ™์ด nextSerialNumber++๊ฐ€ atomicํ•˜์ง€ ์•Š์€ ์—ฐ์‚ฐ์ด๋ผ๋Š” warning์„ ๋„์›Œ์ค€๋‹ค.

volatile์€ ๊ฐ€์‹œ์„ฑ์„ ๋ณด์žฅํ•˜์ง€๋งŒ ์›์ž์„ฑ์€ ๋ณด์žฅํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.


์˜ˆ์ œ #2: synchronized

generateSerialNumber ๋ฉ”์„œ๋“œ์— synchronized ํ•œ์ •์ž๋ฅผ ๋ถ™์ด๋ฉด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. ๋™์‹œ์— ํ˜ธ์ถœํ•ด๋„ ์„œ๋กœ ๊ฐ„์„ญํ•˜์ง€ ์•Š์œผ๋ฉฐ ์ด์ „ ํ˜ธ์ถœ์ด ๋ณ€๊ฒฝํ•œ ๊ฐ’์„ ์ฝ๊ฒŒ ๋œ๋‹ค.

public class SerialNumberSynchronized {

    private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(10);

    private static int nextSerialNumber = 0;

    private static synchronized int generateNextSerialNumber() throws InterruptedException {
        Thread.sleep(1);
        return nextSerialNumber++;
    }

    public static void main(String[] args) throws InterruptedException {
        int N = 30;
        CountDownLatch latch = new CountDownLatch(N);

        for (int i = 0; i < N; i++) {
            THREAD_POOL.execute(() -> {
                try {
                    generateNextSerialNumber();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                latch.countDown();
            });
        }

        latch.await();
        System.out.println(nextSerialNumber);
    }
}

synchronized๋Š” ์›์ž์„ฑ๊ณผ ๊ฐ€์‹œ์„ฑ์„ ๋ชจ๋‘ ๋ณด์žฅํ•œ๋‹ค.


์˜ˆ์ œ #2: atomic object

๋˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” java.util.concurrent.atomic ํŒจํ‚ค์ง€์˜ AtomicInteger๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. synchronized๋Š” Lock์„ ์‚ฌ์šฉํ•ด ๋™๊ธฐํ™”ํ•˜์ง€๋งŒ ์ด ํŒจํ‚ค์ง€์˜ ํด๋ž˜์Šค๋Š” ๋ฝ ์—†์ด ์Šค๋ ˆ๋“œ ์•ˆ์ „ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ๋„ ๋›ฐ์–ด๋‚˜๋‹ค.

public class SerialNumberAtomic {

    private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(10);

    private static final AtomicInteger nextSerialNumber = new AtomicInteger();

    private static int generateNextSerialNumber() throws InterruptedException {
        Thread.sleep(1);
        return nextSerialNumber.getAndIncrement();
    }

    public static void main(String[] args) throws InterruptedException {
        int N = 30;
        CountDownLatch latch = new CountDownLatch(N);

        for (int i = 0; i < N; i++) {
            THREAD_POOL.execute(() -> {
                try {
                    generateNextSerialNumber();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                latch.countDown();
            });
        }

        latch.await();
        System.out.println(nextSerialNumber);
    }
}

atomic object๋Š” ์›์ž์„ฑ๊ณผ ๊ฐ€์‹œ์„ฑ์„ ๋ชจ๋‘ ๋ณด์žฅํ•œ๋‹ค.


์š”์•ฝ

์ด๋ฒˆ ์•„์ดํ…œ์—์„œ ์–ธ๊ธ‰๋œ ๋ฌธ์ œ๋“ค์„ ํ”ผํ•˜๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์• ์ดˆ์— ๊ฐ€๋ณ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด๋‹ค. ๋ถˆ๋ณ€ ๋ฐ์ดํ„ฐ๋งŒ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ์•„๋ฌด๊ฒƒ๋„ ๊ณต์œ ํ•˜์ง€ ๋ง์ž. ๋˜๋„๋ก ๊ฐ€๋ณ€ ๋ฐ์ดํ„ฐ๋Š” ๋‹จ์ผ ์Šค๋ ˆ๋“œ์—์„œ๋งŒ ์“ฐ๋„๋ก ํ•˜์ž.

๐Ÿ’ก ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐ€๋ณ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•œ๋‹ค๋ฉด ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ  ์“ฐ๋Š” ๋™์ž‘์€ ๋ฐ˜๋“œ์‹œ ๋™๊ธฐํ™”ํ•ด์•ผ ํ•œ๋‹ค.



Item 79: ๊ณผ๋„ํ•œ ๋™๊ธฐํ™”๋Š” ํ”ผํ•˜๋ผ

์•„์ดํ…œ 78์—์„œ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์€ ๋™๊ธฐํ™”์˜ ๋ฌธ์ œ๋ฅผ ๋‹ค๋ค˜๋‹ค๋ฉด, ์ด๋ฒˆ ์•„์ดํ…œ์—์„œ๋Š” ๋ฐ˜๋Œ€ ์ƒํ™ฉ์„ ๋‹ค๋ฃฌ๋‹ค. ๊ณผ๋„ํ•œ ๋™๊ธฐํ™”๋Š” ์„ฑ๋Šฅ์„ ์ €ํ•˜์‹œํ‚ค๊ณ , ๊ต์ฐฉ ์ƒํƒœ์— ๋น ๋œจ๋ฆฌ๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ›ผ์†ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ๋ง‰์œผ๋ ค๋ฉด ๋™๊ธฐํ™” ๋ธ”๋ก ์•ˆ์—์„œ๋Š” ์ œ์–ด๋ฅผ ์ ˆ๋Œ€๋กœ ํด๋ผ์ด์–ธํŠธ์— ์–‘๋„ํ•˜๋ฉด ์•ˆ๋œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋™๊ธฐํ™”๋œ ์˜์—ญ ์•ˆ์—์„œ๋Š” ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋Š” ํ˜ธ์ถœํ•˜๋ฉด ์•ˆ ๋˜๋ฉฐ, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋„˜๊ฒจ์ค€ ํ•จ์ˆ˜ ๊ฐ์ฒด๋ฅผ ํ˜ธ์ถœํ•ด์„œ๋„ ์•ˆ ๋œ๋‹ค. ์ด๋Ÿฐ ๋ฉ”์„œ๋“œ๋Š” ์™ธ๊ณ„์ธ ๋ฉ”์„œ๋“œ๋ผ ํ•œ๋‹ค.

์•„๋ž˜์— ๋‚˜์˜ค๋Š” ์˜ˆ์‹œ๋“ค์„ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ํฌ์ŠคํŒ… ์ƒ๋‹จ์— ๊ฑธ์–ด๋‘” ๋งํฌ๋ฅผ ํ†ตํ•ด ์ „์ฒด ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค.


์˜ˆ์ œ #1

์•„๋ž˜ ObservableSet์€ ์–ด๋–ค ์ง‘ํ•ฉ(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);
            }
        }
    }

    public boolean add(E element) {
        boolean added = super.add(element);
        if (added) {
            notifyElementAdded(element);
        }
        return added;
    }
}

๊ด€์ฐฐ์ž๋“ค์€ addObserver์™€ removeObserver ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด ๊ตฌ๋…์„ ์‹ ์ฒญํ•˜๊ฑฐ๋‚˜ ํ•ด์ง€ํ•œ๋‹ค. ๋‘ ๊ฒฝ์šฐ ๋ชจ๋‘ ์•„๋ž˜ ์ฝœ๋ฐฑ ์ธํ„ฐํŽ˜์ด์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค.

@FunctionalInterface
public interface SetObserver<E> {
	// ObservableSet์— ์›์†Œ๊ฐ€ ๋”ํ•ด์ง€๋ฉด ํ˜ธ์ถœ๋œ๋‹ค.
    void added(ObservableSet<E> set, E element);
}

์•„๋ž˜ ํ”„๋กœ๊ทธ๋žจ์€ ์ง‘ํ•ฉ์— ์ถ”๊ฐ€๋œ ์ •์ˆ˜๊ฐ’์„ ์ถœ๋ ฅํ•˜๋‹ค๊ฐ€ ๊ทธ ๊ฐ’์ด 23์ด๋ฉด ๊ตฌ๋…์„ ํ•ด์ง€ํ•˜๋Š” ๊ด€์ฐฐ์ž(SetObserver)๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ์ด ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•˜๋ฉด ์–ด๋–ค ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ฌ๊นŒ?

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

    set.addObserver(new SetObserver<Integer>() {
        @Override
        public void added(ObservableSet<Integer> s, Integer e) {
            System.out.println(e);
            if (e == 23) {
                s.removeObserver(this);
            }
        }
    });

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

0๋ถ€ํ„ฐ 23๊นŒ์ง€ ์ถœ๋ ฅํ•œ ํ›„ ๊ด€์ฐฐ์ž ์ž์‹ ์„ ๊ตฌ๋… ํ•ด์ง€ํ•œ ๋‹ค์Œ ์กฐ์šฉํžˆ ์ข…๋ฃŒ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ConcurrentModificationException ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค!

ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ ์ˆœ์„œ๋ฅผ ์‚ดํŽด๋ณด์ž. main ํ•จ์ˆ˜์˜ for ๋ฌธ์—์„œ i = 23์ผ ๋•Œ

  • (1) set.add(i)๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
  • (2) ObservableSet์˜ set์— ์›์†Œ๋ฅผ ์ถ”๊ฐ€ํ•œ ๋’ค notifyElementAdded๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
  • (3) ObservableSet์˜ observers ๋ฆฌ์ŠคํŠธ๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ added๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
  • (4) 23์„ ์ถœ๋ ฅํ•˜๊ณ  removeObserver๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
  • (5) observers ๋ฆฌ์ŠคํŠธ์—์„œ ์›์†Œ(observer)๋ฅผ ์‚ญ์ œํ•œ๋‹ค.
// (1) main 
set.add(i);

// (2) ObservableSet
public boolean add(E element) {
    boolean added = super.add(element);
    if (added) {
        notifyElementAdded(element);
    }
    return added;
}

// (3) ObservableSet
private void notifyElementAdded(E element) {
    synchronized (observers) {
        for (SetObserver<E> observer : observers) {
            observer.added(this, element);
        }
    }
}

// (4) main
public void added(ObservableSet<Integer> s, Integer e) {
    System.out.println(e);
    if (e == 23) {
        s.removeObserver(this);
    }
}

// (5) ObservableSet
public boolean removeObserver(SetObserver<E> observer) {
    synchronized (observers) {
        return observers.remove(observer);
    }
}

๊ฒฐ๊ตญ ๋™๊ธฐํ™” ๋ธ”๋ก ์•ˆ์—์„œ observers ๋ฆฌ์ŠคํŠธ๋ฅผ ์ˆœํšŒํ•˜๋Š” ๋„์ค‘์— ์›์†Œ๋ฅผ ์‚ญ์ œํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์—์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์ด๋‹ค. ์ด๋Š” ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๋™์ž‘์ด๋ฉฐ ConcurrentModificationException์—๋„ ๋ช…์‹œ๋˜์–ด ์žˆ๋‹ค.

๋™๊ธฐํ™” ๋ฉ”์„œ๋“œ(notifyElementAdded) ์•ˆ์—์„œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋„˜๊ฒจ์ค€ ํ•จ์ˆ˜ ๊ฐ์ฒด(added)๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค!


์˜ˆ์ œ #2

์ด๋ฒˆ์—” removeObserver๋ฅผ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ง์ ‘ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ  ์‹คํ–‰์ž ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•ด ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ํ˜ธ์ถœํ•˜๋„๋ก ํ–ˆ๋‹ค. ์‹คํ–‰ ๊ฒฐ๊ณผ๋Š” ์–ด๋–จ๊นŒ?

public class ObservableTest3 {

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

        set.addObserver(new SetObserver<Integer>() {
            @Override
            public void added(ObservableSet<Integer> s, Integer e) {
                System.out.println(e);
                if (e == 23) {
                	// removeObserver ํ˜ธ์ถœ์„ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ํ˜ธ์ถœํ•˜๋„๋ก ํ•œ๋‹ค.
                    ExecutorService exec = Executors.newSingleThreadExecutor();
                    try {
                        exec.submit(() -> s.removeObserver(this)).get();
                    } catch (ExecutionException | InterruptedException ex) {
                        throw new AssertionError(ex);
                    } finally {
                        exec.shutdown();
                    }
                }
            }
        });

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

์ด ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•˜๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜์ง„ ์•Š์ง€๋งŒ ๊ต์ฐฉ์ƒํƒœ์— ๋น ์ง„๋‹ค. ์•ž์„œ ์‚ดํŽด๋ณธ ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ ์ˆœ์„œ ์ค‘ ์ผ๋ถ€์ด๋‹ค. ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ notifyElementAdded ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉฐ ๋ฝ์„ ๊ฐ–๊ณ  ์žˆ๊ณ , ๊ทธ ๋‹ค์Œ์— ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ƒ์„ฑ๋˜์–ด removeObserver๋ฅผ ์‹คํ–‰ํ•˜์ง€๋งŒ ์ด๋ฏธ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฝ์„ ๊ฐ–๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฝ์„ ์–ป์„ ์ˆ˜ ์—†๋‹ค. ์ด์™€ ๋™์‹œ์— ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ด€์ฐฐ์ž๋ฅผ ์ œ๊ฑฐํ•˜๊ธฐ๋งŒ์„ ๊ธฐ๋‹ค๋ฆฐ๋‹ค.

// (3) ObservableSet
private void notifyElementAdded(E element) {
    synchronized (observers) {
        for (SetObserver<E> observer : observers) {
            observer.added(this, element);
        }
    }
}

// (4) main
public void added(ObservableSet<Integer> s, Integer e) {
    System.out.println(e);
    if (e == 23) {
        ExecutorService exec = Executors.newSingleThreadExecutor();
        try {
            exec.submit(() -> s.removeObserver(this)).get();
        } catch (ExecutionException | InterruptedException ex) {
            throw new AssertionError(ex);
        } finally {
            exec.shutdown();
        }
    }
}

// (5) ObservableSet
public boolean removeObserver(SetObserver<E> observer) {
    synchronized (observers) {
        return observers.remove(observer);
    }
}

์˜ˆ์ œ #3

์™ธ๊ณ„์ธ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์„ ๋™๊ธฐํ™” ๋ธ”๋ก ๋ฐ”๊นฅ์œผ๋กœ ์˜ฎ๊ธฐ๋ฉด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค! notifyElementAdded ๋ฉ”์„œ๋“œ์—์„œ ๊ด€์ฐฐ์ž ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณต์‚ฌํ•ด ์“ฐ๋ฉด ๋ฝ ์—†์ด๋„ ์•ˆ์ „ํ•˜๊ฒŒ ์ˆœํšŒํ•  ์ˆ˜ ์žˆ๋‹ค.

private void notifyElementAdded(E element) {
    List<SetObserver<E>> snapShot = null;
    synchronized (observers) {
        snapShot = new ArrayList<>(observers);
    }
    for (SetObserver<E> observer : snapShot) {
        observer.added(this, element);
    }
}

์ด ๋ฐฉ์‹์„ ์ ์šฉํ•˜๋ฉด ์•ž์„œ ์‚ดํŽด๋ณธ ๋‘ ์˜ˆ์ œ์—์„œ ๋ฐœ์ƒํ–ˆ๋˜ ์˜ˆ์™ธ์™€ ๊ต์ฐฉ์ƒํƒœ๊ฐ€ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๋™๊ธฐํ™” ์˜์—ญ ๋ฐ”๊นฅ์—์„œ ํ˜ธ์ถœ๋˜๋Š” ์™ธ๊ณ„์ธ ๋ฉ”์„œ๋“œ๋ฅผ ์—ด๋ฆฐ ํ˜ธ์ถœ(open call)์ด๋ผ ํ•œ๋‹ค. ์™ธ๊ณ„์ธ ๋ฉ”์„œ๋“œ๋Š” ์–ผ๋งˆ๋‚˜ ์˜ค๋ž˜ ์‹คํ–‰๋ ์ง€ ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๋™๊ธฐํ™” ์˜์—ญ ์•ˆ์—์„œ ํ˜ธ์ถœ๋œ๋‹ค๋ฉด ๊ทธ๋™์•ˆ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๋Š” ๋ณดํ˜ธ๋œ ์ž์›์„ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๊ณ  ๋Œ€๊ธฐํ•ด์•ผ ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์—ด๋ฆฐ ํ˜ธ์ถœ์€ ์‹คํŒจ ๋ฐฉ์ง€ ํšจ๊ณผ ์™ธ์—๋„ ๋™์‹œ์„ฑ ํšจ์œจ์„ ํฌ๊ฒŒ ๊ฐœ์„ ํ•ด์ค€๋‹ค.


์˜ˆ์ œ #4

์™ธ๊ณ„์ธ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์„ ๋™๊ธฐํ™” ๋ธ”๋ก ๋ฐ”๊นฅ์œผ๋กœ ์˜ฎ๊ธฐ๋Š” ๊ฒƒ๋ณด๋‹ค ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์€ ์ž๋ฐ”์˜ ๋™์‹œ์„ฑ ์ปฌ๋ ‰์…˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ CopyOnWriteArrayList๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

CopyOnWriteArrayList๋Š” ArrayList๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๋กœ, ๋‚ด๋ถ€๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์ž‘์—…์€ ํ•ญ์ƒ ๋ณต์‚ฌ๋ณธ์„ ๋งŒ๋“ค์–ด ์ˆ˜ํ–‰ํ•˜๋„๋ก ๊ตฌํ˜„๋˜์—ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฐฐ์—ด์„ ์ˆ˜์ •ํ•  ์ผ์ด ๋งŽ๋‹ค๋ฉด ์„ฑ๋Šฅ ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒ ์ง€๋งŒ, ์ˆœํšŒํ•  ๋•Œ ๋ฝ์ด ํ•„์š” ์—†์–ด ๋งค์šฐ ๋น ๋ฅด๋‹ค.

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

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

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

    public void addObserver(SetObserver<E> observer) {
        observers.add(observer);
    }

    public boolean removeObserver(SetObserver<E> observer) {
        return observers.remove(observer);
    }

    private void notifyElementAdded(E element) {
        for (SetObserver<E> observer : observers) {
            observer.added(this, element);
        }
    }

    public boolean add(E element) {
        boolean added = super.add(element);
        if (added) {
            notifyElementAdded(element);
        }
        return added;
    }
}

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์•ž์„œ ์‚ดํŽด๋ณธ ๋‘ ์˜ˆ์ œ์—์„œ ๋ฐœ์ƒํ–ˆ๋˜ ์˜ˆ์™ธ์™€ ๊ต์ฐฉ์ƒํƒœ๊ฐ€ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.


์š”์•ฝ

๊ธฐ๋ณธ ๊ทœ์น™์€ ๋™๊ธฐํ™” ์˜์—ญ์—์„œ๋Š” ๊ฐ€๋Šฅํ•œ ์ผ์„ ์ ๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋˜ํ•œ, ๊ต์ฐฉ์ƒํƒœ์™€ ๋ฐ์ดํ„ฐ ํ›ผ์†์„ ํ”ผํ•˜๋ ค๋ฉด ๋™๊ธฐํ™” ์˜์—ญ ์•ˆ์—์„œ ์™ธ๊ณ„์ธ ๋ฉ”์„œ๋“œ๋ฅผ ์ ˆ๋Œ€ ํ˜ธ์ถœํ•˜์ง€ ๋ง์ž.



Reference

0๊ฐœ์˜ ๋Œ“๊ธ€