동시성 컬렉션

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

동시성 컬렉션

자바에서 컬렉션 프레임워크는 원자적인 연산을 제공할까? 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개의 댓글

관련 채용 정보