Post

자바 Thread_11


원자적 레퍼런스

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
public static class StandardStack<T> {
        private StackNode<T> head;
        private int counter = 0;

        public synchronized void push(T value) {
            StackNode<T> newhead = new StackNode<>(value);
            newhead.next = head;
            head = newhead;
            counter++;
        }

        public synchronized T pop() {
            if(head == null) {
                counter++;
                return null;
            }

            T value = head.value;
            head = head.next;
            counter++;
            return value;
        }

        private static class StackNode<T> {
            public T value;
            public StackNode<T> next;

            public StackNode(T value) {
              this.value = value;
              this.next = next;
            }
        }
    }
  • 동기화를 안썻다면 push와 pop의 헤드를 바꾸는 곳에서 경쟁상태가 일어나 다른스레드가 읽기와 쓰기 연산사이에 헤드의 값을 바꿀수있어 원자성이 보존안될 가능성이 매우큼



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
    public static class LockFreeStack<T> {
        // AtomicReference를 통해 객체를 원자적으로 만들 수 있음.
        private AtomicReference<StackNode<T>> head = new AtomicReference<>();
        private AtomicInteger counter = new AtomicInteger(0);

        public void push(T value) {
            StackNode<T> newhead = new StackNode<>(value);

            // 많은 스레드가 동시에 푸시하는 상황이기에 정상적으로 성공하려면 여러번 시도를 해야하기때문 
            while (true) {
              // get()을 통해 현재 스택의 헤드를 가져옴.
                StackNode<T> cruuent = head.get();
                // 새로운헤드의 다음 노드를 기존노드를 가리키게함
                newhead.next = cruuent;
                // 그런데 AtomicReference 헤드는 계속 current 헤드를 가리키고 있음
                // compareAndSet()를 통해 현재 헤드의 값과 같으면 새로운 헤드를 할당함
                if(head.compareAndSet(cruuent, newhead)){
                    break;
                } else{
                  // 실패하면 아주 약간 쉼
                    LockSupport.parkNanos(1);
                }
            }
            counter.incrementAndGet();
        }

        public T pop() {
            StackNode<T> current = head.get();
            StackNode<T> newHead;

            while (current != null) {
                // push 메서드의 반대로임
                newHead = current.next;
                if(head.compareAndSet(current,newHead)){
                    break;
                } else {
                    LockSupport.parkNanos(1);
                    current = head.get();
                }
            }
            counter.incrementAndGet();

            return current != null ? current.value : null;
        }

        public int getCount() {
            return counter.get();
        }
    }
  • 현재 헤드 값을 읽은 다음, 해당 값을 기반으로 헤드를 대체할 새 후보자 값을 계산하고 방금 읽는값이 안변해야함
  • compareAndSet() 사용해 예상값이 여전히 같은지 확인하고 새로운 값을 할당
  • 만약 예상값이 틀렸다면 false를 반환해 else를 만나서 다시 새롭게 프로세스를 반복



  • 중요한것은 락을 쓰는거보다 원자적 객체일떄 항상 성능이 더 좋은것은 아님 !!

  • 위의 코드를 비교하면 성능차이가 나는것을 알 수 있음.


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

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