-
Thread Safety에 대해 알아보자One Cookie a day 2023. 9. 22. 20:31
Thread
- 프로세스가 할당받은 자원을 이용하는 실행 흐름의 단위
- Thread끼리 메모리의 특정 부분을 공유하며 작동
- Code : 프로그램의 코드 / 명령어들이 기계어로 저장된 영역
- Data : 코드에서 선언된 전역 / 정적 변수가 저장되는 영역 -> 실행 시 할당되고 종료 시 소멸
- Stack : 컴파일 타임에 크기 결정, 지역변수, 매개변수, 리턴 값 등 저장
- Heap : 런타임에 크기가 결정, 사용자에 의해 메모리 공간이 동적으로 할당되고 해제되는 영역, 참조형 등 저장
- 독립적으로 가지는 부분 : program counter, register set, stack
- 다른 스레드와 공유하는 부분 : code, data, OS resources
Thread-Safety?
* 멀티 스레드 간 동기화 문제란?
- 어느 스레드가 어떤 순서로 실행될 지 순서 알 수 없음.
- 스레드가 함께 전역변수를 사용한다면 다른 프로세스가 접근을 못하거나, 바뀐 자원에 접근해 버리는 등 충돌이 발생할 위험
- 운영체제가 자동으로 해 주는 것이 아니므로, 프로그래머가 직접 적합하게 프로그래밍해야 함
= 여러 스레드에서 동일한 객체에 선언된 메소드에 접근하여 데이터를 처리하려 할때 발생하는 문제
여러 스레드에서 동시에 동일한 객체에 선언된 메소드에 접근하여 데이터를 처리해도, 정확하게 동작한다면 Thread-Safe 하다고 할 수 있습니다.
Thread Safety를 보장하는 방법?
1. synchronized
- 여러 스레드가 하나의 자원을 사용하고자 할 때, 현재 데이터를 사용하고 있는 해당 스레드를 제외하고 나머지 스레드들은 데이터에 접근할 수 없도록 막는 키워드
- 메서드에 붙이거나 메서드 내 블록 단위에 붙일 수 있음
- Lock을 걸어 다른 스레드의 접근을 막기 때문에 멀티 스레드 간 가시성 문제와 동시 접근 문제를 해결하여 thread-safe
- 남용하게 되면 스레드들이 lock을 얻기 위해 대기해야 하므로 성능 저하 문제가 발생할 수 있음
- Atomic은 무슨 자료구조?
- package java.util.concurrent.atomic에 존재.
- CAS 알고리즘 이용하여 현재 스레드가 가진 값과 메모리에 저장된 값 비교 후 다르면 스레드를 동기화시킴
- 따라서 synchronzied 처럼 락 걸지 않고, 쓰기 작업 스레드가 하나 이상이어도 된다.
- Volatile로 값이 선언되어 있다.
- 각 스레드가 가진 CPU cache에서 값을 가져오지 않고, 공유하는 Main memory에서 읽고 쓰는 방법
- 따라서 단순 Volatile로 동시성 문제를 해결하려면 쓰기 작업을 수행하는 스레드는 하나여야 함.
- 이 경우 값을 쓸 때는 CAS 알고리즘을 통해 동시성을, 값을 읽어올때는 메모리에서 읽어와서 해결
- 값을 쓸 때 비교 후 작성한다
- public final boolean compareAndSet(int expect, int update)
- return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
public class AtomicInteger extends Number implements java.io.Serializable { private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; // ... }
2. 메소드 내 지역변수 활용
- 위에서 언급한 대로, 각 스레드는 Stack을 독립적으로 할당받는다.
- Stack에는 지역변수, 리턴 값, 매개변수 등이 저장되는데, 이 Stack은 스레드끼리 공유하지 않는다.
3. Thread-Safe한 자료구조 사용
- Concurrent Package 자료구조들
- ex) Concurrent Hash Map
- put, remove, clear 등의 함수에 내부적으로 synchronized 키워드 구현
- ex) Concurrent Hash Map
4. 불변객체 활용하기
- 생성 후 그 상태를 바꿀 수 없는 객체
- 객체가 가리키고 있는, 힙 영역에 있는 데이터 자체를 변경할 수 없다.
- Immutable하게 만들면 값이 변경되지 않기 때문에, thread-safe하다.
5. ThreadLocal
- 각 스레드별로 할당되는 변수로, 고유한 값들을 가지며 스레드별로 독립적인 접근을 보장한다.
- 내부적으로 Map에 저장되어 있다. (ThreadLocalMap - Thread 정보를 키로 삼아 저장 )
아래는 오라클 공식 문서에서 TreadLocal 페이지에 들어가면 볼 수 있는 예시코드이다.
import java.util.concurrent.atomic.AtomicInteger; public class ThreadId { // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); } }
참고글
'One Cookie a day' 카테고리의 다른 글
Transaction에 대해서 알아보자 (0) 2023.10.27 싱글톤 디자인 패턴에 대해 (0) 2023.10.16 Java 7 이상에서 ArrayList 크기 계산 시 비트 연산자를 활용하는 이유 (0) 2023.10.10 ArrayList는 어떻게 동적으로 크기를 늘릴 수 있을까? (0) 2023.10.09 Java 5부터 등장한 Generic은 하위 버전에 어떻게 호환이 되는걸까? (0) 2023.09.30