티스토리 뷰

CS/Java

자바에서 동시성 문제를 해결하는 방법

기억용블로그 2022. 9. 17. 21:36
728x90

동시성 문제

동시성 문제란 멀티스레드의 환경에서 하나의 리소스에 여러 스레드가 동시다발적으로 접근하게 되어 같은 리소스에서 값을 가져오더라도 서로 다른 값을 가져오는 등의 문제가 발생하는 경우를 일컫는다.

 

이러한 동시성 문제가 발생하지 않도록 애플리케이션을 설계하는 것을 Thread Safe라 한다.

 

자바에서는 이러한 동시성 문제를 해결하기위해 아래과 같은 방법을 이용한다.

 

synchronized, volatile

synchronized (Pessimistic Locking, 비관적 잠금)

synchronized 키워드를 사용하면 공유 변수에 동시에 접근하는 스레드를 단 하나로 한정지음으로서 다른 모든 스레드들은 공유 변수에 접근할 수 없고 이용 중인 스레드가 릴리즈 할 때까지 대기하게 된다.

 

synchronized 키워드의 정확한 의미에 대해서 좀 더 자세히 알아보자. #

어떤 스레드가 작업을 하기 위해 공유 변수를 가져와야 한다고 할때 데이터가 필요할 때마다 RAM에 접근하는 방식은 매우 느리기에 CPU의 L1, L2, L3 캐시와 같은 하이라키 구조에 데이터를 일정량 가져와서 저장하고 캐싱된 데이터를 이용하는 방식으로 진행된다.

 

 

synchronized는 공유변수를 이용하고 있던 스레드에서 해당 데이터를 다시 RAM에 적재할 때까지 다른 스레드들이 접근하지 못 하게 하는 키워드이다.

즉, 공유 변수 캐싱 -> 스레드에서 캐싱된 데이터로 작업 -> 캐싱된 데이터로 공유 변수에 업데이트하면서 lock을 release 하는 일련의 과정에 전부 lock이 걸리므로 동시성(modify)과 가시성(read)이라는 문제를 한번에 해결할 수 있다.

 

When to use

공유 변수에 여러 스레드가 동시적으로 읽고 업데이트하려고 하는 경우에 해당 키워드를 사용하여 동시성과 가시성을 보장할 수 있다.

하지만 synchronized 는 동기/블로킹으로 동작하기에 성능상에 문제가 발생할 수 있다.

 

volatile

캐싱된 데이터를 사용하지 않고 언제나 주기억장치에서 새로운 데이터를 가져오게 하는 키워드로 동작한다.

언제나 데이터를 주기억장치에서 가져오기 때문에 서로 다른 스레드들간에 똑같은 공유 변수에서 다른 값을 가져올 수 있는 문제를 해결하여 가시성을 보장받을 수 있다.

 

When to use

하나의 스레드만 공유 변수를 modify하고 다른 스레드들에서는 해당 변수를 read하기만 하는 경우에 사용하여 언제나 최신 값을 보장받게 한다.

 

하지만 volatile은 가시성에 대해서만 보장을 해주는 것이므로 동시성의 race condition은 해결하지 못 한다.

 

AtomicXXX, Concurrent

AtomicXXX (Optimistic Locking, 낙관적 잠금)

synchronized와 달리 lock을 걸지 않으면서(lock-free) thread safe를 보장하는 방법이다.

synchronized의 경우 lock에 의해 블로킹을 동작하여 성능상의 이슈가 발생할 수 있지만 atomic은 논블로킹의 CAS 알고리즘으로 동작하여 해당 문제를 해결한다.

 

CAS 알고리즘은 다음과 같은 3가지 파라미터를 받는다.

  • 내부 값을 변경하기 원하는 V의 메모리 주소
  • 스레드가 가장 마지막으로 알고 있는 이전 값 A
  • V에 새로 업데이트하고자 하는 새로운 값 B

 

이때 CAS가 하고자 하는 행동은 다음과 같다.

CAS: "내가 알고 있기로는 V는 A라는 값을 가지고 있을거야. 만약 맞다면 A를 B로 업데이트해주고, 틀렸다면 값을 변경하지 말고 나에게 틀렸다고 알려줘."

 

CAS는 성공할 것이라는 생각을 가지고 업데이트를 진행하고 이후에 다른 스레드에 의해 값이 변경되었을 경우 실패를 감지하기 때문에 낙관적 테크닉으로 분류된다.

 

 

자바의 AtomicInteger는 내부적으로 다음과 같이 구현되어 있다. #

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);
    }	
}

 

Concurrent

자바의 Concurrent 패키지 내에는 이러한 동시성을 해결하기 위한 여러 자료구조가 존재하는데 어떤 식으로 구현되어 있는지에 따라 lock이나 동시성 처리가 달라진다.

 

예를 들어, ConcurrentHashMap은 segmented lock 기법을 이용하여 동시성을 처리하기 때문에 lock-free 하지 않지만, ConcurrentLinkedQueue의 경우 lock-free하게 구현되어 있다. #

 

레퍼런스

https://stackoverflow.com/questions/9749746/what-is-the-difference-between-atomic-volatile-synchronized

 

What is the difference between atomic / volatile / synchronized?

How do atomic / volatile / synchronized work internally? What is the difference between the following code blocks? Code 1 private int counter; public int getNextUniqueIndex() { return count...

stackoverflow.com

https://howtodoinjava.com/java/multi-threading/compare-and-swap-cas-algorithm/

 

Java Compare and Swap Example - CAS Algorithm

Compare and Swap says "I think V should have the value A; if it does, put B there, otherwise don’t change it but tell me I was wrong." CAS is an optimistic technique—it proceeds with the update in the hope of success, and can detect failure if another

howtodoinjava.com

 

'CS > Java' 카테고리의 다른 글

자바의 OOP  (0) 2022.10.25
JSON을 파싱하는 방법  (0) 2022.10.21
자바의 <<, >>, >>>와 그의 활용  (0) 2022.09.06
자바의 변수 종류 정리  (0) 2022.08.31
자바 8, 11, 17 버전별 기능 차이  (0) 2022.08.26
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함