강의 메모 - Atomic Variables - 단일연산변수 - 1,2 #
개요 #
- 단일연산변수는 락을 사용하지 않고도 여러 스레드 간에 안전하게 값을 공유하고 동기화하는 데 사용되며 기본적으로 volatile 의 속성을 가지고 있다
- 단일연산변수는 원자적인(read-modify-write) 연산을 지원하여 내부적으로 Compare and Swap (CAS) 연산을 사용하여 데이터의 일관성과 안정성을 유지한다
- 단일연산변수는 간단한 연산의 경우 락을 사용하는 것보다 월등히 빠른 성능을 보여 주지만 연산이 복잡거나 시간이 오래 걸리는 작업은 락을 사용하는 것보다 오버헤드가 커질 수 있다.
- 단일연산변수는 단일 연산에 대해 원자성을 보장하지만 여러 연산을 조합한 복잡한 동작에 대해서는 원자성이 보장되지 않을 수 있으며 강력한 동기화 메커니즘을 고려해야 한다
- 각각의 API를 통해서 여러 연산을 조합할 경우
- SELECT가 끼어들 수 있다.
- 언제든지 main memory의 값이 변경될 수는 있다.
단일 연산 클래스(Atomic Class) #
- 단일연산 변수를 사용하기위한 여러종류의 단일연산 클래스가 제공된다
클래스 #
공통 API #
AtomicBoolean #
// 초기 값이 false 로 설정된다
AtomicBoolean()
// 초기 값이 지정된 값으로 설정된다
AtomicBoolean(boolean initialValue)
// 현재 값을 가져온다
boolean get()
// 새로운 값으로 설정한다
void set(boolean newValue)
// 현재 값을 가져오고 새로운 값을 설정한다
boolean getAndSet(boolean newValue)
// 현재 값이 기대한 값과 같으면 새로운 값을 설정하고 true 를 반환하고
일치하지 않으면 아무런 동작없이 false 를 반환한다
boolean compareAndSet(boolean expect, boolean update)
AtomicBoolean atomicBool = new AtomicBoolean(true);
boolean currentValue = atomicBool.get();
System.out.println("Current value: " + currentValue); // true
atomicBool.set(false);
System.out.println("New value: " + atomicBool.get()); // false
boolean previousValue = atomicBool.getAndSet(true); System.out.println("Previous value: " + previousValue); // false System.out.println("New value: " + atomicBool.get()); // true
public class AtomicBooleanExample {
private static AtomicBoolean flag = new AtomicBoolean(false);
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
while (!flag.compareAndSet(false, true)){ // Busy waiting..} ;
//critical section
flag.set(false);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
while (!flag.compareAndSet(false, true)){// Busy waiting..} ;
//critical section
flag.set(false);
}
});
thread1.start();
thread2.start();
}
}
AtomicInteger #
// 초기 값이 false 로 설정된다
AtomicInteger()
// 초기 값이 지정된 값으로 설정된다
AtomicInteger(int initialValue)
// 현재 값을 반환하고 +1을 증가시킨다
int getAndIncrement()
// 현재 값에 +1을 증가하고 결과값을 반환한다
int incrementAndGet()
// 현재 값을 반환하고 -1을 감소시킨다
int getAndDecrement()
// 현재 값을 반환하고 지정된 값 만큼 더한다
int getAndAdd(int delta)
// 현재 값에 지정된 값 만큼 더하고 결과 값을 반환한다
int addAndGet(int delta)
// 현재 값에 -1 을 감소시키고 결과 값을 반환한다
int decrementAndGet()
// 현재 값을 반환하고 람다 함수로 실행된 값으로 업데이트 한다
int getAndUpdate(IntUnaryOperator updateFunction)
// 람다 함수로 실행된 값으로 업데이트하고 결과값을 반환한다
int updateAndGet(IntUnaryOperator updateFunction)
AtomicInteger atomicInt = new AtomicInteger(10);
int currentValue = atomicInt.get();
System.out.println("Current value: " + currentValue); // 10
atomicInt.set(20);
System.out.println("New value: " + atomicInt.get()); // 20
int previousValue = atomicInt.getAndSet(30);
System.out.println("Previous value: " + previousValue); // 20 System.out.println("New value: " + atomicInt.get()); // 30
int newValue = atomicInt.incrementAndGet();
System.out.println("New value after increment: " + newValue); // 31
boolean updated = atomicInt.compareAndSet(31, 40); System.out.println("Update successful? " + updated); // true
System.out.println("New value: " + atomicInt.get()); // 40
IntUnaryOperator addFive = value -> value + 5;
int previousValue = atomicInt.getAndUpdate(addFive); System.out.println("Previous value: " + previousValue); // 40 System.out.println("Updated value: " + atomicInt.get()); // 45
AtomicRefernece
#
// 초기 값이 null 로 설정된다
AtomicReference()
// 초기 값이 지정된 값으로 설정된다
AtomicReference(V initialValue)
// 현재 값을 가져온다
V get()
// 새로운 값으로 설정한다
void set(V newValue)
// 현재 값을 가져오고 새로운 값을 설정한다
V getAndSet(V newValue)
// 현재 값이 기대한 값과 같으면 새로운 값을 설정하고 true 를 반환하고
일치하지 않으면 아무런 동작없이 false 를 반환한다
boolean compareAndSet(V expect, V update)
// 현재 값을 반환하고 람다 함수로 실행된 값으로 업데이트 한다
V getAndUpdate(UnaryOperator<V> updateFunction)
// 람다 함수로 실행된 값으로 업데이트하고 결과값을 반환한다
V updateAndGet(UnaryOperator <V> updateFunction)
AtomicReference<String> reference = new AtomicReference<>("Initial Value");
String currentValue = reference.get();
System.out.println("Current value: " + currentValue); // Initial Value
reference.set("New Value");
System.out.println("New value: " + reference.get()); // New Value
boolean success = reference.compareAndSet(" New Value ", "Updated Value"); System.out.println("Update successful? " + success); // true
System.out.println("Current value: " + reference.get()); // Updated Value
String oldValue = reference.getAndSet("Final Value");
System.out.println("Old value: " + oldValue); // Updated Value
System.out.println("Current value: " + reference.get()); // Final Value
UnaryOperator<String> operator = oldValue -> oldValue + " is correct";
String newValue = reference.updateAndGet(operator);
System.out.println("New value: " + newValue); // Final Value is correnct
System.out.println("Current value: " + reference.get()); // Final Value is correnct
Atomic*FieldUpdater - 단일연산필드 업데이터 #
- 지정된 클래스의 volatile 필드에 대한 원자적 업데이트를 가능하게 하는 리플렉션 기반 유틸리티이다
- 지정된 객체가 필요하다.
- 주로 클래스 내부의 필드를 원자적으로 변경하는 경우에 사용된다
사용 #
클래스 #
생성 #
공통 API #
AtomicIntegerFieldUpdater 기본 구현 #
- 필드 volatile 선언 필요
Atomic * FieldUpdater vs AtomicVariable #
- 우측이 더 MyClass의 복잡도가 있다.
- 클래스 객체의 생성이 많을수록 좌측이 낫다.
- 간단한 방식은 우측이 더 나을수도 있다.
AtomicReferenceFieldUpdater 동기화 예제 #
- String 타입의 message 객체 (volatile)
- this 의 message 필드의 값이 "" 일때 “Hello World!“로 업데이트
- if문 빠져나올때 다시 ““으로 업데이트
- 그래서 먼저 진입한 쓰레드가 수행
- 그리고 더 늦게 진입한 다른 쓰레드가 오면 if 문은 false 이므로 else문으로 빠진다.