자바 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.