본문 바로가기

java

[JAVA] AtomicInteger, 쓰레드 동기화(synchronization) , Atomic, CAS 알고리즘 이해하기

AtomicInteger 을 자세히 이해하기 위한 흐름을 정리해 보았습니다.

모든 내용은 출처가 있습니다.

 

 

[목차] AtomicInteger 란? 

0. 쓰레드란?

1. 쓰레드의 동기화 

    1) 기본 synchronized 를 이용한 동기화

2. Atomic 에 대해서 

    1) CAS Algorithm

    2) ABA Problem

3. Atomic class 에 대해서

4. AtomicInteger 상세 확인하기

 

 

 

AtomicInteger 란? 

AtomicInteger 란 원자성을 보장하는 Interger를 의미한다. 멀티 쓰레드 환경에서 동기화 문제를 별도의 synchronized 키워드 없이 해결하기 위해서 고안된 방법이다.

(일반적으로 동기화 문제는 synchronized,  Atomic,  volatile 세가지 키워드로 해결한다.) 


출처: https://javaplant.tistory.com/23 [자바공작소]

 

 

 

 

0.  쓰레드란? 

 

 - 어떠한 프로그램 내에서, 특히 프로세스(process) 내에서 실행되는 흐름의 단위. 

- 스레드는 하나의 프로세스 내부에 포함되는 함수들이 동시에 실행 될 수 있는 한 작은 단위 프로세서(lighweight process) 

- CPU 를 사용하게 하는 기본단위

- 하나의 프로세스에 포함된 다수의 스레드들은 프로세스의 메모리 자원들(code section, data secition, Heap 등 ) 과 운영체제의

  운영 자원들( 예 : 파일 입출력 등)을 공유한다. 

 

 

 

- 일반적으로 한 프로그램은 하나의 thread 를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 thread 를 동시에 실행할 수 있다. 이를 멀티 스레드(mulithread) 라 한다. 

 - 프로세스는 각각 개별적인 code, data, file 을 가지나, 스레드는 자신들이 포함된 프로세스의 code, data, file 을 공유한다.

 

 

[사진, 내용 출처 - http://contents.kocw.or.kr/KOCW/document/2015/yeungnam/kimyoungtak/10-2.pdf]

 

 

 

1. 쓰레드의 동기화(synchronization)

 

한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 '쓰레드의 동기화(synchronization)' 라고 한다. 

 

  싱글 쓰레드 프로세스의 경우 프로세스 내에 단 하나의 쓰레드만 작업하기 때문에 프로세스의 자원을 가지고 작없하는데 별 문제가 없지만, 멀티 쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게 된다. 만일 쓰레드 A가 작업하던 도중에 다른 쓰레드 B에게 제어권이 넘어갔을때,  쓰레드 A가 작업하던 공유 데이터를 쓰레드 B가 임의로 변경 하였다면, 다시 쓰레드 A가 제어권을 받아서 나머지 작업을 받아서 나머지 작업을 마쳤을때 원래 의도 했던 것과 다른 결과를 얻을 수 있다. 

 

[이미지 출처 - https://codedragon.tistory.com/3529]

 

 

 

 synchronized은 특정 Thread가 해당 블락 전체를 lock 하기 때문에 다른 Thread는 아무작업을 못하고 기다리는 상황이 되어 낭비가 심하다. 그래서 NonBlocking하면서 동기화 문제를 해결하기 위한 방법이 Atomic이다.

 

+ 'java.util.concurrent.locks'

+ 'java.util.concurrent.atomic' 의 패키지를 JDK 1.5 부터 지원한다.

 

 1) 기본 synchronized 를 이용한 동기화

 먼저 가장 간단한 동기화 방법인 Synchronized 키워드를 이용한 동기화에 대해서 알아보겠다.

1. 메서드 전체를 임계 영역으로 지정
public synchronized void calcSum(){
  // ... 
}

2.특정한 영역을 임계 영역으로 지정
synchronized(객체의 참조변수) {
  // ... 
}

1. 메서드 앞에 synchronized 붙이면 메서드 전체가 임계 영역으로 설정된다. 쓰레드는 synchronized 를 붙이면 메서드 전체가 임계 영역으로 설정된다. 쓰레드는 synchronized 메서드가 호출된 시점부터 해당 메서드가 포함된 객체의 lock 을 얻어 작업을 수행하다가 메서드가 종료되면 lock 을 반환한다. 

 

2. 메서드 내의 일부 코드를 블럭{}  으로 감싸고 블럭앞에 'synchronized(참조변수)' 를 붙이는 것인데, 이때 참조 변수는 락을 걸고자 하는 객체를 참조하는 것이어야 한다. 이 블럭은 영역 안으로 들어가면서부터 쓰레드는 지정된 객체의 lock 을 얻게 되고, 이 블럭을 벗어나면 lock 을 반납한다.

 

두 방법 모두 lock 을 하나씩 가지고 있으며, 해당 객체의 lock 을 가지고 있는 쓰레드만 임계 영역의 코드를 수행할 수 있다. 그리고 다른 쓰레드 들은 lock 을 얻을때까지 기다리게 된다. 임계 영역은 멀티쓰레드 프로그램의 성능을 좌우하기 때문에 가능하면 메서드 전체에 락을 거는 것보다 synchronized 블럭으로 임계 영역을 최소화해서 보다 효율적인 프로그램이 되도록 노력해야 한다. 

 

 

2.  Atomic 에 관해서 

AtomicInterger 동작의 핵심 원리는 바로 CAS알고리즘(Compare and Swap)에 있다.

 1) CAS Algorithm

 CAS(compare and swap)는 optimistic loking 을 실현하는 동시성 제어 기술이며 3개의 파라미터 CAS( V, e , n) 을 가지고 있다.

 

V 는 업데이트될 변수(메모리 값),
e 는 이전 예상 값,
n 은 업데이트될 예상값 

 

 

V(업데이트될 변수(메모리값)) 이 E의 값과 같으면 V의 값은 N으로 설정된다. V의 값이 E의 값과 다르면 

다른 스레드가 없데이트 되었음을 의미하여 현재 스레드는 아무것도 하지 않고 현재 V의 실제 값을 반환한다. 

전체 작업이 원자성을 띄고 있기 때문에 CAS 알고리즘은 Non-Bocking 이라고 할수 있다. 

 

public class TestCompareAndSwap {

    private static CompareAndSwap cas = new CompareAndSwap();

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                public void run() {
                    //Get estimates
                    int expectedValue = cas.get();
                    boolean b = cas.compareAndSet(expectedValue, (int) (Math.random() * 101));
                    System.out.println(b);
                }
            });
        }
    }
}

class CompareAndSwap {

    private int value;

    //Get memory value
    public synchronized int get() {
        return value;
    }

    //Comparison
    public synchronized int compareAndSwap(int expectedValue, int newValue) {
        //Read memory value
        int oldValue = value;
        //Comparison
        if (oldValue == expectedValue) {
            this.value = newValue;
        }
        return oldValue;
    }

    //Settings
    public synchronized boolean compareAndSet(int expectedValue, int newValue) {
        return expectedValue == compareAndSwap(expectedValue, newValue);
    }
}

[코드 출처 - https://developpaper.com/cas-algorithm-and-java-atomic-class/ ]

 

 

 

 

+ 메모리 관점에서도 정리된 내용이 있어 퍼왔다.

 

출처: https://javaplant.tistory.com/23 [자바공작소]

 

 멀티 쓰레드 환경, 멀티 코어 환경에서 각 CPU는 메인 메모리에서 변수값을 참조하는게 아닌, 각 CPU의 캐시 영역에서 메모리를 값을 참조하게 된다. ([그림2] CPU 캐시 메모리 참고) 이때, 메인 메모리에 저장된 값과 CPU 캐시에 저장된 값이 다른 경우가 있다. (이를 가시성 문제라고 한다.) 그래서 사용되는 것이 CAS 알고리즘이다. 현재 쓰레드에 저장된 값과 메인 메모리에 저장된 값을 비교하여 일치하는 경우 새로운 값으로 교체하고, 일치 하지 않는 다면 실패하고 재시도를 한다. 이렇게 처리되면 CPU캐시에서 잘못된 값을 참조하는 가시성 문제가 해결되게 된다.

출처: https://javaplant.tistory.com/23 [자바공작소]

 

 

 

 2) ABA Problem

참고 하기: https://chfhrqnfrhc.tistory.com/entry/ABA-Problem-1?category=509454 

 

ABA Problem

1. ABA Problem이란? 간단히 말하자면 CAS(Compare And Swap)를 사용해서 자료구조의 아이템을 변경할 때, 포인터가 시스템에 의해 재사용되면서 생기는 문제다. 일단 CAS를 비교할 때 포인터 변수를 가지

chfhrqnfrhc.tistory.com

 

 

3. Atomic class 

 

1. Basic type 

 - AtomicBoolean

 - AtomicInteger

 

[사용 예제]

 

AtomicInteger 을 사용하면 동기화된 블록은 더 이상 필요하지 않다.

AtomicInteger 클래스가 값을 읽고 쓰는 동안 변경되지 않는 다는걸 보장해 주기 때문이다. 

Public class SharedState { 

  @Test 
  public void sharedState(){ 
    final ExecutorService executorService = Executors.newCachedThreadPool(); 
    final AtomicCounter counter = new AtomicCounter(); 
   
    executorService.execute(new CounterSetter(counter)); 
    final int value = counter.getNumber().incrementAndGet(); 
    assertEquals(1, value); 
  } 
  private static class CounterSetter implements Runnable{ 
      private final AtomicCounter counter; 
      public CounterSetter(AtomicCounter counter) { 
      	this.counter = counter; 
    }

    @Override public void run() { 
    	while(true){ 
        	counter.getNumber().set(0); 
        } 
    } 
  } 
  public class AtomicCounter{ 
    private final AtomicInteger number = new AtomicInteger(0); 
    public AtomicInteger getNumber() { 
      return number; 
    } 
  } 
}

출처: https://cornswrold.tistory.com/278 [평범한개발자노트]

[AtomicInterger Decompile하여 요약된 내용] 

public class AtomicInteger extends Number implements java.io.Serializable 
{ 
	private volatile int value; 
    
	public final int incrementAndGet() { 
      int current; 
      int next; 
      do{ 
      	current = get(); 
        next = current + 1; 
      } while (!compareAndSet(current, next)); 
      return next; 
      } 
      public final boolean compareAndSet(int expect, int update) { 
      	return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 
      } 
}

출처: https://javaplant.tistory.com/23 [자바공작소]

 

 - AtomicLong

 

2. Reference type

 - AtomicMarkableReference

 - AtomicStampedReference

 

3. Array type 

 - AtomicIntegerArray

 - AtomicLongArray

 - AtomicReferenceArray

 

4. Object attribute type

 - AtomicIntegerFieldUpdater

 - AtomicLongFieldUpdater

 - AtomicReferenceFieldUpdater

 

5. High performance atomic class

 - Java 8 new atomic class, using the idea of segmentation 

 

http://contents.kocw.or.kr/KOCW/document/2015/yeungnam/kimyoungtak/10-2.pdf

https://developpaper.com/cas-algorithm-and-java-atomic-class/

https://codedragon.tistory.com/3529

반응형