Java Adv 19 - 자바 동시성 컬렉션
in Programming Language on Java
자바 synchronized 프록시
자바에서 제공하는 java.util 패키지의 컬렉션 프레임워크는 대부분 스레드 안전(Thread Safe)하지 않다. ArrayList, LinkedList, HashSet, HashMap 등의 기본 자료 구조들은 여러 연산들이 동시에 이루어지기 때문에 동기화가 필요하다. 그러나 모든 자료 구조에 synchronized를 사용하여 동기화를 해두는 것은 성능 저하를 초래할 수 있다. 동기화는 스레드 안전을 보장하지만, 멀티스레드 환경에서만 필요한 기능이다. 동기화를 미리 적용하면 단일 스레드 환경에서도 불필요한 성능 저하가 발생할 수 있다. 따라서 동기화의 필요성을 정확히 판단하고, 꼭 필요한 경우에만 동기화를 적용하는 것이 중요하다. Vector는 과거에 ArrayList와 비슷한 기능을 제공하며, 모든 메서드에 synchronized를 적용하여 동기화를 보장했다. 그러나 이로 인해 단일 스레드 환경에서 성능이 저하되었고, 결국 널리 사용되지 않게 되었다. 지금은 하위 호환성을 위해 남아있지만, 다른 대안들이 존재하기 때문에 사용을 권장하지 않는다. 대신, 동기화를 필요에 따라 적용할 수 있는 방법으로 프록시 패턴을 사용할 수 있다. 예를 들어, Collections.synchronizedList() 메서드를 사용하면, ArrayList를 동기화된 리스트로 변환할 수 있다. 이 메서드는 내부적으로 SynchronizedRandomAccessList라는 클래스를 사용하여 동기화를 적용한다. 이렇게 하면 기존 코드의 구조를 유지하면서, 필요한 경우에만 동기화를 적용할 수 있다.
List<String> list = Collections.synchronizedList(new ArrayList<>());
위 코드에서 synchronizedList() 메서드는 ArrayList를 감싸는 동기화된 리스트를 반환한다. 이 리스트의 메서드는 내부적으로 synchronized 키워드를 사용하여 동기화를 보장한다. 예를 들어, add() 메서드는 다음과 같이 동기화된 코드로 구현된다.
public boolean add(E e) {
synchronized (mutex) {
return c.add(e);
}
}
Collections는 이와 같이 여러 종류의 동기화 프록시 메서드를 제공하여, List, Set, Map 등의 컬렉션을 동기화된 버전으로 변환할 수 있게 해준다. 예를 들어, synchronizedMap(), synchronizedSet() 등의 메서드를 통해 동기화된 컬렉션을 생성할 수 있다. 하지만 synchronized 프록시를 사용하는 방식에는 단점이 있다. 첫째, 동기화 오버헤드가 발생한다. 모든 메서드 호출 시마다 동기화가 이루어지기 때문에 성능이 저하될 수 있다. 둘째, 전체 컬렉션에 대해 동기화가 이루어지기 때문에 잠금 범위가 넓어지고, 이는 잠금 경합(lock contention)을 증가시켜 병렬 처리의 효율성을 저하시킬 수 있다. 셋째, 선택적인 동기화가 어려워, 과도한 동기화로 이어질 수 있다. 따라서 자바는 이러한 단점을 보완하기 위해 java.util.concurrent 패키지에서 더 정교한 동시성 컬렉션(concurrent collection)을 제공한다. 이 컬렉션들은 동기화 오버헤드를 최소화하고, 동기화의 효율성을 높이며, 멀티스레드 환경에서 성능을 극대화할 수 있도록 설계되었다.
Reference
김영한님의 자바 강의