039 Atomic

강의 메모 - Atomic Variables - 단일연산변수 - 1,2 #

개요 #

  • 단일연산변수는 락을 사용하지 않고도 여러 스레드 간에 안전하게 값을 공유하고 동기화하는 데 사용되며 기본적으로 volatile 의 속성을 가지고 있다
  • 단일연산변수는 원자적인(read-modify-write) 연산을 지원하여 내부적으로 Compare and Swap (CAS) 연산을 사용하여 데이터의 일관성과 안정성을 유지한다
  • 단일연산변수는 간단한 연산의 경우 락을 사용하는 것보다 월등히 빠른 성능을 보여 주지만 연산이 복잡거나 시간이 오래 걸리는 작업은 락을 사용하는 것보다 오버헤드가 커질 수 있다.
  • 단일연산변수는 단일 연산에 대해 원자성을 보장하지만 여러 연산을 조합한 복잡한 동작에 대해서는 원자성이 보장되지 않을 수 있으며 강력한 동기화 메커니즘을 고려해야 한다
    • 각각의 API를 통해서 여러 연산을 조합할 경우
    • SELECT가 끼어들 수 있다.
    • 언제든지 main memory의 값이 변경될 수는 있다. img.png

단일 연산 클래스(Atomic Class) #

  • 단일연산 변수를 사용하기위한 여러종류의 단일연산 클래스가 제공된다

클래스 #

img_1.png

공통 API #

img_2.png

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 필드에 대한 원자적 업데이트를 가능하게 하는 리플렉션 기반 유틸리티이다
    • 지정된 객체가 필요하다.
  • 주로 클래스 내부의 필드를 원자적으로 변경하는 경우에 사용된다

사용 #

클래스 #

img_6.png

생성 #

img_7.png

공통 API #

img_8.png

AtomicIntegerFieldUpdater 기본 구현 #

img_3.png

  • 필드 volatile 선언 필요

Atomic * FieldUpdater vs AtomicVariable #

  • 우측이 더 MyClass의 복잡도가 있다.
  • 클래스 객체의 생성이 많을수록 좌측이 낫다.
  • 간단한 방식은 우측이 더 나을수도 있다. img_4.png

AtomicReferenceFieldUpdater 동기화 예제 #

  • String 타입의 message 객체 (volatile)
  • this 의 message 필드의 값이 "" 일때 “Hello World!“로 업데이트
  • if문 빠져나올때 다시 ““으로 업데이트
  • 그래서 먼저 진입한 쓰레드가 수행
  • 그리고 더 늦게 진입한 다른 쓰레드가 오면 if 문은 false 이므로 else문으로 빠진다. img_5.png