java.util 패키지 내 컬렉션 프레임워크의 대부분은 thread-safe 하지 않다.
이러한 문제점을 자바에서는 어떻게 해결하였는지 살펴보자.
Vector와 synchronized최초에 자바는 이러한 문제를 해결하기 위해 JDK 1.0에서 java.util 패키지에서 Vector 클래스를 지원하였다.
하지만 아래 코드와 같이 synchronized 를 통해 동기화를 구현하여 단일 스레드에서도 불필요한 성능 저하가 발생하게 되었고, 지금은 거의 사용되지 않게 되었다.
현재는 하위 호환을 위해 존재하나, 사용을 권장하지 않는다.
package java.util;
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
@SuppressWarnings("serial") // Conditionally serializable
protected Object[] elementData;
protected int elementCount;
protected int capacityIncrement;
... 중략 ...
public synchronized void copyInto(Object[] anArray) {
System.arraycopy(elementData, 0, anArray, 0, elementCount);
}
public synchronized int capacity() {
return elementData.length;
}
public synchronized int size() {
return elementCount;
}
public synchronized boolean isEmpty() {
return elementCount == 0;
}
public synchronized int indexOf(Object o, int index) {
if (o == null) {
for (int i = index ; i < elementCount ; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = index ; i < elementCount ; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
... 중략 ...
}
Collections.synchronizedXX()프록시 패턴을 이용하여 Collection 타입 객체를 synchronized 블록 내에서 동작하도록 구현한 기능이다.
synchronizedCollection()public static <T> Collection<T> synchronizedCollection(Collection<T> c) {
return new SynchronizedCollection<>(c);
}
synchronizedList()public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
synchronizedMap()public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}
synchronizedSet()public static <T> Set<T> synchronizedSet(Set<T> s) {
return new SynchronizedSet<>(s);
}
synchronizedNavigableMap()public static <K,V> NavigableMap<K,V> synchronizedNavigableMap(NavigableMap<K,V> m) {
return new SynchronizedNavigableMap<>(m);
}
synchronizedNavigableSet()public static <T> NavigableSet<T> synchronizedNavigableSet(NavigableSet<T> s) {
return new SynchronizedNavigableSet<>(s);
}
synchronizedSortedMap()public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m) {
return new SynchronizedSortedMap<>(m);
}
synchronizedSortedSet()public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s) {
return new SynchronizedSortedSet<>(s);
}
NavigableMap과NavigableSet이란?
주어진 검색 대상에 가장 가까운 일치 항목을 반환하는 탐색 메서드를 제공하기 위해SortedMap과SortedSet을 확장하였으며, 각각 정렬된 맵과 집합을 지원한다.
두 인터페이스의 대표적인 구현체로는TreeMap,TreeSet이 있다.
SynchronizedList를 예시로 살펴보자.
구현체 내부에 list 멤버변수를 두어 생성자로 넘겨 받은 List 객체로 설정한다.
이후 get(), set(), add(), remove()와 같은 동작에서 synchronized 블록 내에서 기존 객체의 동작을 수행하도록 구현되어 있다.
편리하게 이용 가능하지만 동기화 오버헤드로 인한 성능 저하, 동기화 최적화가 불가하다는 한계가 존재한다.
static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> {
@SuppressWarnings("serial") // Conditionally serializable
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return list.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return list.hashCode();}
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
public int indexOf(Object o) {
synchronized (mutex) {return list.indexOf(o);}
}
public int lastIndexOf(Object o) {
synchronized (mutex) {return list.lastIndexOf(o);}
}
public boolean addAll(int index, Collection<? extends E> c) {
synchronized (mutex) {return list.addAll(index, c);}
}
... 중략 ...
}
java.util.concurrentLinkedHashSet, LinkedHashMap과 같이 입력 순서를 유지하는 동시에 thread-safe한 Set, Map 구현체는 제공하지 않는다.
ListCopyOnWriteArrayListArrayList의 thread-safe한 버전SetCopyOnWriteArraySetCopyOnWriteArrayList를 사용하는 SetConcurrentSkipListSetConcurrentSkipListMap에 기반한 확장 가능한 동시 탐색 가능한 구현체Comparator 사용 가능)MapConcurrentHashMapthread-safe 하지만 검색 연산에는 락을 사용하지 않으며, 모든 액세스를 방지하는 방식으로 전체 테이블을 잠그는 기능은 지원되지 않음 ConcurrentSkipListMapConcurrentNavigableMap의 구현체Comparator 사용 가능)QueueConcurrentLinkedQueuethread-safe 큐DequeConcurrentLinkedDequethread-safe DequeaddAll(), removeIf() 또는 forEach()와 같이 여러 요소를 대량으로 추가, 제거 또는 조회하는 연산은 원자적으로 수행된다고 보장할 수 없음