Atomic

배고픈 징징이 ㅣ 2023. 2. 27. 14:48

1.  Atomic 란?

원자성을 보장하는 변수

Java의 동시성 문제를 해결하는 방법 중 하나

CAS ( Compare And Swap ) 알고리즘을 사용

 

2. CAS ( Compare And Swap ) 란?

현재 스레드가 CPU Cache Memory와 Main Memory의 자원의 값을 비교하여,
일치하는 경우 새로운 값으로 교체, 일치하지 않는다면 실패하고 재시도하는 알고리즘

이렇게 처리하면 CPU Cache Memory에 갱신되지 않은 잘못된 값을 참조하는 가시성 문제를 해결할 수 있다.

 

3. 멀티 스레드 이슈

 

Java 메모리 구조

멀티스레드 환경에서 CPU는 메인 메모리에서 변수값을 참조하지 않고, 각자의 CPU Cache Memory에서 참조한다.

  1.  CPU가 작업을 처리하기 위해 데이터를 RAM에서 읽어 CPU Cache Memory에 복제
  2.  작업을 처리한 뒤, 변경된 CPU Cache Memory 데이터를 RAM에 덮어 씌운다.

이렇게 되면, 메인 메모리에 저장된 값과 CPU 캐시 메모리에 저장된 값이 다른 경우가 발생된다.

가시성 문제와 동시 접근 문제로 나뉜다.

 

4. 가시성 문제

하나의 스레드에서 공유 자원을 수정한 결과가 다른 스레드에서 보이지 않는 경우 발생

public class Test {
    static boolean keepGoing = true;
    
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while(keepGoing){}
            System.out.println("The End Of Thread");
        });

        thread.start();
        TimeUnit.SECONDS.sleep(1);
        keepGoing = false;
        System.out.println("The End Of Main");
    }
}

위의 코드를 보고 예상을 해보자면, 출력 순서는 The End Of Thread 후에 The End Of Main이 출력될거라 예상이된다.

하지만 실제로 코드를 실행해보면, The End Of Main만 출력 된다.

이런 문제를 가시성 문제라고 하는데, thread변수의 Thread와 main의 Thread는 각자의 CPU 캐시 메모리에 복제된 keepGoing을 바라보기 때문에, main에서 변경된 keepGoing을 tread는 알 수 없다.

 

5. 동시접근 문제

여러 스레드에서 공유자원에 동시 접근을 했을때, 연산이 가장 늦게 끝난 스레드의 변경된 데이터로 메인 메모리에 덮어 씌워진다.

public class Test {
    static int count = 0;
    
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++){
            new Thread(() -> {
                for (int j = 0; j < 1000; j++){
                    System.out.println(count++);
                }
            }).start();
        }
    }
}

위의 코드를 보고 예상을 해보면, 100개의 스레드들이 각자 1~1000까지 순서대로 출력을 할 것 같지만.

실제로 실행을 해보면 뒤죽박죽으로 숫자들이 출력된다.

이는 스레드들이 하나의 공유자원에 동시접근을 하면서 발생되는 문제이다.

 

6. 해결책

  1. synchronized 키워드 사용 : 여러 스레드에서 공유자원에 동시 접근할 경우, 처음 접근한 스레드가 작업을 끝낼때 까지
    자원에 Lock을 걸어 다른 스레드의 접근을 차단한다.
    → 가시성 문제와 동시접근 문제 해결, 하지만 다른 스레드들의 대기 상태로 자원을 낭비하게 된다.

  2. volatile 키워드 사용 : CPU Cache Memory가 아닌 Main Memory에서 자원을 참조한다.
    하나의 스레드만 Write가 가능하고, 다른 스레드들은 Read만 할 수 있다는 가정하에 동시성이 보장된다.
    결국 가시성 문제는 해결할 수 있지만, 동시접근 문제는 해결할 수 없다.

  3. Atomic 변수 사용 : synchronized 키워드 없이 동시성 문제를 해결하기 위해 고안된 방법
    원자성을 보장하는 변수.
    CAS 알고리즘을 사용하여 동시성을 보장한다. (synchronized 보다 적은 비용으로 동시성 보장)
반응형

'Java' 카테고리의 다른 글

Process & Thread / Thread.run() & Thread.start()  (0) 2023.04.19
Excel Download With POI  (0) 2023.03.21
Consumer  (0) 2023.02.09
Try-With-Resources  (0) 2023.02.09
Static & Singleton Pattern  (0) 2023.02.08