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);
}
위 코드는 스레드에 안전하지 못하다. 만약 delete연산과 getLast 연산 사이에 스레드 전환이 생겼을 경우 ArrayIndexOutOfBoundsException 이 발생할 수 있다.
따라서 이 코드는 다음과 같이 바뀌어야한다.
public static Object getLast(Vector list) {
syncronized(list) { // 안전성 확보
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
}
public static void deleteLast(Vector list) {
syncronized(list) { // 안전성 확보
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
}
동기화된 컬렉션 클래스는 대부분 클라이언트 측 락을 사용할 수 있도록 만들어져 있기 때문에, 이를 사용하여 새로운 기능을 만드는 클래스는 동일한 수준의 동기화 수단을 적용해야 한다.
동기화된 컬렉션 역시 다중 연산을 사용할 때 발생하는 문제점을 해결하지는 못한다.
Iterator를 사용해 컬렉션을 읽는다 해도 반복문이 실행되는 동안 다른 스레드가 컬렉션에 내용을 추가/제거하는 변경 작업을 시도할 때 발생할 수 있는 문제를 막아주지는 못한다.
동기화된 컬렉션의 Iterator를 사용한다 해도 다른 스레드가 같은 시점 컬렉션을 변경하는 작업을 처리하지 못하게 만들어져 있고 대신 즉시 멈춤(fail-fast)의 형태로 구현되어 있다.
Iterator 순회 도중 컬렉션에 변경이 생기면 ConcurrentModificationException 예외가 발생된다.List<String> list = Collections.syncronizedList(...);
for (String item : list) {
doSomething(item); // 이런 작업은 좋지 않다
}
위 코드에서 doSomething 작업에서 다른 Lock 을 확보해야 하는 상황이 일어날 경우 데드락이 발생할 가능성도 높아진다. 검증된 doSomething 에게 작업을 위임해야한다.
순회 중 다른 스레드에 의한 변경을 막기위해 다음과 같이 전체를 동기화 하는 것은 좋지 않다.
List<String> list = Collections.syncronizedList(...);
syncronized(list) {
for (String item : list) {
// ...
}
}
왜냐하면, list 내용이 굉장히 많다면 동시성이 매우 비효율적이기 때문이다. 반복문에서 락을 오래 잡고 있으면 스레드 대기 상태가 많아지고 대기 스레드가 적체될수록 성능이 나빠지기 때문이다.
컬렉션 자체를 clone 하는 방법도 있지만, 컬랙션이 클 경우 clone 에 시간이 오래걸릴 수 있다. 따라서 작업에 걸리는 시간 / 복사본 생성 비용 / 응답성 등을 고려해서 적용을 해야한다.