ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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 키워드 구현 

     

    4. 불변객체 활용하기 

    불변객체, Immutable Object란 무엇인가?  

    • 생성 후 그 상태를 바꿀 수 없는 객체
    • 객체가 가리키고 있는, 힙 영역에 있는 데이터 자체를 변경할 수 없다. 
    • 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();
         }
     }

     

     

     

     

     

     

     

     

     

    참고글 

Designed by Tistory.