동시성 컬렉션

이동건 (불꽃냥펀치)·2025년 1월 15일

동시성 컬렉션

자바에서 컬렉션 프레임워크는 원자적인 연산을 제공할까? ArrayList 인스턴스에 여러 스레드가 동시에 접근해도 될까? 여러 스레드가 동시에 접근해도 괜찮은 경우를 스레드 세이프라고한다.
그렇다면 ArrayList는 스레드 세이프일까? 이 부분을 자세히 알기위해 임시로 예제 리스트를 구현해 보겠다.

public class BasicList implements SimpleList {
	private static final int DEFAULT_CAPACITY = 5;
    
    private Object[] elementData;
    private int size = 0;
    
    public BasicList() {
        elementData = new Object[DEFAULT_CAPACITY];
	}
    
    @Override
    public int size() {
        return size;
    }
    @Override
    public void add(Object e) {
		elementData[size] = e;
		sleep(100); // 멀티스레드 문제를 쉽게 확인하는 코드 size++;
	}
    @Override
    public Object get(int index) {
        return elementData[index];
    }
    @Override
    public String toString() {
        return Arrays.toString(Arrays.copyOf(elementData, size)) + " size=" +
		size + ", capacity=" + elementData.length;
	} 
}
  • 이 자료 구조에서 add()는 원자적 계산이기때문에 여러 스레드가 동시에 접근해도 아무런 문제가 없다
public void add(Object e) {
		elementData[size] = e;
		sleep(100); // 멀티스레드 문제를 쉽게 확인하는 코드 
        size++;
}
  • 이 메서드는 데이터 하나를 추가하는 기능을 제공한다
  • 그와 더불어서 자료구조의 길이를 늘이는 추가 기능이 더해졌다.
  • size++ 자체가 원자적인 연산이 아니라서 멀티스레드 상황에서 사용하려면 synchronized , Lock 등을 사용해서 동기화를 해야한다.
public class SimpleListMainV2 {
    public static void main(String[] args) throws InterruptedException {
        test(new BasicList());
    }
    private static void test(SimpleList list) throws InterruptedException {
        log(list.getClass().getSimpleName());
// A를 리스트에 저장하는 코드
		Runnable addA = new Runnable() {
            @Override
            public void run() {
                list.add("A");
                log("Thread-1: list.add(A)");
            }
	};
// B를 리스트에 저장하는 코드
		Runnable addB = new Runnable() {
            @Override
            public void run() {
                list.add("B");
                log("Thread-2: list.add(B)");
            }
	};
        Thread thread1 = new Thread(addA, "Thread-1");
        Thread thread2 = new Thread(addB, "Thread-2");
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        log(list);
	}
}

실행결과

 09:48:13.989 [     main] BasicList
 09:48:14.093 [ Thread-1] Thread-1: list.add(A)
 09:48:14.096 [ Thread-2] Thread-2: list.add(B)
 09:48:14.096 [     main] [B, null] size=2, capacity=5
  • 실행결과를 보면 size는 2인데 데이터는 B 하나만 입력되어있다
public void add(Object e) {
		elementData[size] = e; // 스레드1, 스레드2 동시에 실행 
        sleep(100);
		size++;
}

예측 상황

  • 스레드1 수행: elementData[0]=A,elemenetData[0]의 값은 A가 된다
  • size++이 실행되어 size=1
  • 스레드2 수행: elementData[0]=B,elemenetData[0]의 값은 B가 된다
  • size++이 실행되어 size=2

정리

  • 컬렉션 프레임워크 대부분은 스레드 세이프 하지 않다
  • 데이터를 추가하는 add() 와 같은 연산은 마치 원자적인 연산처럼 느껴진다. 하지만 그 내부에서는 수 많은 연산들이 함께 사용된다. 배열에 데이터를 추가하고, 사이즈를 변경하고, 배열 을 새로 만들어서 배열의 크기도 늘리고, 노드를 만들어서 링크에 연결하는 등 수 많은 복잡한 연산이 함께 사용된다
  • 이 문제를 해결하려면 synchronized , Lock 등을 사용해서 동기화 처리를 하면 된다


프록시 도입

프록시를 사용해서 프록시가 대신 동기화 기능을 처리할 수 있다.

public class SyncProxyList implements SimpleList {
     private SimpleList target;
     public SyncProxyList(SimpleList target) {
         this.target = target;
	}
     @Override
     public synchronized void add(Object e) {
         target.add(e);
     }
     @Override
     public synchronized Object get(int index) {
         return target.get(index);
     }
     @Override
     public synchronized int size() {
         return target.size();
     }
      @Override
    public synchronized String toString() {
        return target.toString() + " by " + this.getClass().getSimpleName();
    }
}
  • 프록시 역할을 하는 클래스이다
  • SyncPoxylistBasicList와 같이 SimpleList인터페이스를 구현한다
  • 이 클래스는 빈껍데기처럼 모든 메서드에 synchronized 를 걸어주는 일 뿐 이다
  • 이 프록시 클래스는 synchronized 만 걸고, 그 다음에 바로 실제 호출해야 하는 원본 대상( target )을 호출 한다




프록시 패턴

위의 방식이 바로 프록시 패턴이다. 프록시 패턴은 객체지향 디자인 패턴 중 하나로 어떤 객체에 대힌 접근을 제어하기 위해 그 객체의 대리인 또는 인터페이스 역할을 하는 객체를 제공해주는 패턴이다.

  • 접근제어: 실제 객체에 대한 접근을 제한하거나 통제할 수 있다
  • 성능 향상: 실제 객체의 생성을 지연하거나 캐싱하여 성능을 최적화 할 수 있다
  • 부가 기능 제공: 실제 객체에 추가적인 기능을 투명하게 제공할 수 있다







출처: https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard

profile
자바를 사랑합니다

0개의 댓글