앞서 기존 컬렉션 프레임워크에는 synchronized가 적용되어있지 않기 때문에,
프록시 패턴을 사용해서 synchronized를 적용하는 예제를 만들어봤습니다
그런데 만약 이렇게 만들지 않고 자바에서 모든 자료구조에 synchronized를 미리 적용해둔다면 어떨까요?
synchronized, Lock, CAS등 모든 방식은 결국 성능과 트레이드 오프가 있습니다
결국 동기화를 사용하지 않는 것이 가장 빠르다고 할 수 있습니다
컬렉션 프레임워크는 항상 멀티스레드에서 사용되는 것이 아닙니다
미리 동기화를 해둔다면 단일 스레드 환경에서 사용할 때도 동기화로 인해 성능이 저하됩니다
동기화가 필요한 경우만 동기화를 적용하는 것이 필요합니다
과거 자바에서 이러한 실수를 한 사례가 바로 java.util.Vector
클래스입니다
ArrayList와 같은 기능을 제공하는데 메소드에 synchronized를 통한 동기화가 되어있습니다
이 클래스는 단일 스레드 환경에서도 불필요한 동기화로 인해 성능저하가 발생했고
결과적으로 거의 사용하지 않고 있습니다
지금은 하위호환을 위해 남겨져있고 다른 대안이 많기 때문에 사용을 권장하지 않습니다
가장 좋은 대안은 앞서 만든 synchronized를 적용해주는 프록시를 만드는 방법이 있습니다
자바는 이러한 컬렉션을 위한 프록시 기능을 제공합니다
List<Integer> list = Collections.synchronizedList(new ArrayList<>());
관련 코드를 더 살펴보면 아래와 같습니다
public static <T> List<T> synchronizedList(List<T> list){
return new SynchronizedRandomAccessList<>(list);
}
앞서 만든 예제와 비슷한 방식으로 프록시 패턴을 적용해서
synchronized 코드블록을 제공하고 이후 관련 기능을 실행하는 것을 확인할 수 있습니다
Collection는 다음과 같은 다양한 synchronized동기화 메소드를 지원합니다
Collections가 제공하는 동기화 프록시 덕분에 스레드 세이프하지 않은 컬렉션을
편리하게 스레드 세이프한 컬렉션으로 변경해서 사용할 수 있습니다
하지만 synchronized프록시를 사용하는 방식도 단점이 존재합니다
synchronized 키워드가 멀티스레드 환경에서 안전한 접근을 보장하지만,
각 메소드 호출시마다 동기화 비용이 추가됩니다
이것 때문에 성능저하가 발생할 수 있습니다
전체 컬렉션에 대한 동기화가 이루어지기 때문에, 잠금범위가 넓어질 수 있습니다
이것은 잠금 경합(Lock Contention)을 증가시키고, 병렬 처리의 효율적을 저하시키는 원인이 될 수 있습니다
또한 모든 메소드에 동기화를 적용하다보면 특정 스레드가 컬렉션을 사용하고 있을 때
다른 스레드들이 대기해야하는 상황이 빈번해질 수 있습니다
synchronized 프록시를 사용하면 컬렉션 전체에 대한 동기화가 이루어지지만,
특정 부분이나 메소드에 대해 선택적으로 동기화를 적용하는 것은 어렵습니다
이것은 과도한 동기화로 이루어질 수 있습니다
즉 프록시 패턴을 적용하는 방식은 모든 메소드에 synchronized를 걸어버리는 것입니다
따라서 동기화에 대한 최적화가 이루어지지 않습니다
자바는 이런 단점을 보완하기 위해 java.util.concurrent
패키지를 통해
동시성 컬렉션(Concurrent Collection)을 제공합니다