JDK 오래된 버전부터 동기화된 컬렉션 클래스 Vector 와 HashTable 이 포함되어 있었고, JDK 1.2 버전부터 Collection.synchronized… 메서드로 동기화된 클래스를 만들어 사용할 수 있다.
여러 클래스가 동기화된 컬렉션을 하나만 놓고 동시에 내용을 변경하려고 하면 컬렉션 클래스가 올바른 방법으로 동작하지 않을 수 있다.
[예시] Vector 클래스에 getLast() 와 deleteLast() 를 정의할 때
public static Object getLast(Vector list) {
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
public static void deleteLast(Vector list) {
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
만약 스레드 A 가 getLast() 를 호출하고, 스레드 B 가 deleteLast() 를 호출할 때, 스레드 A 와 스레드 B 가 섞여 동작한다면 ArrayIndexOutOfBoundsException 이 발생할 수 있다.
Vector 의 입장에서는 스레드 안전성에 문제가 없는 상태이지만, getLast(), deletLast() 의 입장에서는 스레드 안정성에 문제가 있는 상태이다.
동기화된 컬렉션 클래스는 대부분 클라이언트 측 락을 사용할 수 있도록 만들어져 있기 때문에, 컬렉션 클래스가 사용하는 락을 함께 사용하면 추가할 기능을 다른 메서드와 같은 수준으로 동기화시킬 수 있다.
public static Object getLast(Vector list) {
synchronized (list) {
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
}
public static void deleteLast(Vector list) {
synchronized (list) {
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
}
[예시] 컬렉션 클래스에 들어 있는 모든 값을 반복적으로 가져오는 기능을 구현할 때
for(int i = 0; i < vector.size(); i++) {
doSomething(vector.get(i));
}
클라이언트 측의 락을 사용하면 예외 상황이 발생하지 않도록 동기화시킬 수 있다. 단, 반복문이 실행하는 동안 Vector 클래스의 동시 작업을 막아 병목이 될 수 있다.
synchronized (vector) {
for (int i = 0; i < vector.size(); i++)
doSomething(vector.get(i));
}
Vector 외에 새로 추가된 컬렉션 클래스 역시 다중 연산을 사용할 떄 발생하는 문제점을 해결하지는 못한다.
<aside> 💡 변경 횟수를 확인하는 부분이 동기하되어 있지 않아 반복문에서 변경 횟수를 셀 때 스테일 값을 사용하게 될 가능성도 있고, 변경 작업이 있었다는 것을 모를 수도 있다. → 하지만 동기화를 할 경우 성능을 떨어뜨릴 수 있기 때문에 정확한 동기화 기법은 적용하지 않는다.
</aside>