Post

자바 Thread_4

스레드 간 데이터 공유

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public static void main(String[] args) throws InterruptedException {
        // 하나의 인스턴스로 아이템 객체를 주입
        InvertoryCounter invertoryCounter = new InvertoryCounter();
        IncrementingThread incrementingThread = new IncrementingThread(invertoryCounter);
        DecrementingThread decrementingThread = new DecrementingThread(invertoryCounter);

        /* 아이템을 넣는 스레드가 시작되고 종료후, 
           아이템을 빼는 스레드가 시작되어 0이 나옴 */
        incrementingThread.start();
        incrementingThread.join();

        decrementingThread.start();
        decrementingThread.join();


        /* 스레드가 서로 데이터를 공유하고 있기 때문에 
           실행할때마다 값이 달라짐 */
        incrementingThread.start();
        decrementingThread.start();

        incrementingThread.join();
        decrementingThread.join();

        System.out.print("현재 아이템수: ");
        System.out.println(invertoryCounter.getItems());
    }

    // 아이템을 빼는 스레드
    public static class DecrementingThread extends Thread {

        private InvertoryCounter invertoryCounter;

        public DecrementingThread(InvertoryCounter invertoryCounter) {
            this.invertoryCounter = invertoryCounter;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                invertoryCounter.decrement();
            }
        }
    }

    // 아이템을 넣는 스레드
    public static class IncrementingThread extends Thread {

        private InvertoryCounter invertoryCounter;

        public IncrementingThread(InvertoryCounter invertoryCounter) {
            this.invertoryCounter = invertoryCounter;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                invertoryCounter.increment();
            }
        }
    }


    private static class InvertoryCounter {
        private int items = 0;

        public void increment() {
            items++;
        }

        public void decrement() {
            items--;
        }

        public int getItems() {
            return items;
        }
    }


  • 한 스레드는 1을 더하는 작업, 다른 하나는 1을 빼는 작업

  • 실행되는 순서는 스케줄링을 하는 방식에 따라 달라지기에 결과값이 항상 바뀜.

대참사가 일어나는 시나리오

  1. currenVal <- item = 0 (In스레드)
  2. newVal <- currenVal + 1 = 1 (In스레드)
  3. currenVal <- item = 0 (De스레드)
  4. newVal <- currenVal + 1 = 1 (De스레드)
  5. Items <- newVal = -1 (De스레드)
  6. Items <- newVal = 1 (In스레드)

=> 최종적으로는 Items에 1이 할당되어 De스레드에서 일어난 일이 아무것도 아니게 되는 결과가 발생되었음 ! (5번과 6번이 바뀌어도 대참사)




개선하기 !



임계영역

  • 멀티 스레드에 의해 동시 실행되지않게 보호해야하는 코드가 있는 영역


  1. Synchronized - monitor
    • 여러 개의 스레드가 코드 블록이나 전체 메서드에 액세스할 수 없도록 설계된 락킹 메커니즘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static class InvertoryCounter {
        private int items = 0;

        public synchronized void increment() {
            items++;
        }

        public synchronized void decrement() {
            items--;
        }

        public synchronized int getItems() {
            return items;
        }
    }
  • 하나의 스레드가 접근하면 다른 스레드는 접근자체가 안됨.
  • 인스턴스(객체)를 기준으로 동기화가 걸림 => Thread A가 메서드를 실행하면 다른 메서드는 모두 실행할 수 가 없음.



  1. Synchronized - Lock
    • 코드의 블럭을 정의하고 Synchronized 키워드를 통해 전체 메서드는 동기화하지않고 특정 영역만 액세스함
    • 스레드간 동기화가 필요한 부분을 적게 만들 수 있는 장점이 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    private static class InvertoryCounter {
        private int items = 0;

        Object lock = new Object();

        public void increment() {
            synchronized (this.lock) {
                items++;
            }
        }

        public void decrement() {
            synchronized (this.lock) {
                items--;
            }
        }

        public int getItems() {
            synchronized (this.lock) {
                return items;
            }
        }
    }
  • 동기화 블록이나 메서드는 Reentrant 즉, 재진입할 수 있는 요소
  • 스레드1이 이미 다른 동기화 메서드나 블록에 있는 상태에서 또 동기화 메서드에 액세스하면 별 문제 없이 그 동기화 메서드에 액세를 할 수 있게됨.
  • 기본적으로는 스레드가 임계영역에 접근하는 것 자체를 막을 순 없음


출처 - https://kmooc.udemy.com/course/java-multi-threading/ (Java 멀티스레딩, 병행성 및 성능 최적화 - 전문가 되기 )

This post is licensed under CC BY 4.0 by the author.