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.concurrent
LinkedHashSet
, LinkedHashMap
과 같이 입력 순서를 유지하는 동시에 thread-safe
한 Set
, Map
구현체는 제공하지 않는다.
List
CopyOnWriteArrayList
ArrayList
의 thread-safe
한 버전Set
CopyOnWriteArraySet
CopyOnWriteArrayList
를 사용하는 Set
ConcurrentSkipListSet
ConcurrentSkipListMap
에 기반한 확장 가능한 동시 탐색 가능한 구현체Comparator
사용 가능)Map
ConcurrentHashMap
thread-safe
하지만 검색 연산에는 락을 사용하지 않으며, 모든 액세스를 방지하는 방식으로 전체 테이블을 잠그는 기능은 지원되지 않음 ConcurrentSkipListMap
ConcurrentNavigableMap
의 구현체Comparator
사용 가능)Queue
ConcurrentLinkedQueue
thread-safe
큐Deque
ConcurrentLinkedDeque
thread-safe
DequeaddAll()
, removeIf()
또는 forEach()
와 같이 여러 요소를 대량으로 추가, 제거 또는 조회하는 연산은 원자적으로 수행된다고 보장할 수 없음