컬렉션 객체를 순회하면서 특정 값을 삭제하고 싶을 때 어떻게 처리할까
왜 동시성 문제일까? class 파일을 분석해보자
향상된 for문 문법을 포함하는 class 파일
위 코드에서 향상된 for문이 Iterator 객체로 호출되어 while에서 hasNext() 로 바뀐 로직을 볼 수 있다
그런데 remove를 호출하는 부분에서 루프를 돌고있는 객체에서 삭제하는 부분이 있다. 이 부분이 문제가 되어 동시성 문제가 발생했을 것이라 예측하고, Iterator와 remove(obj) 부분을 더 살표보자
[iterator] 를 살펴보자
ArrayList에서 iterator()는 내부클래스인 Itr를 반환한다
Itr 클래스는 클래스 변수로 리스트의 데이터 변경 여부를 체크하고 있다
// ArrayList.class
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // next를 호출했을 때 반환할 element의 index
int lastRet = -1; // 마지막 element의 index
int exepctedModCount = modCount; // List가 수정된 횟수
Itr() {}
...
}
Iterator의 remove는 ArrayList에 구현되어있고, fastRemove를 호출해서 삭제한다
Iterator의 next() 메서드로 다음 element를 가져오려고 시도할 때 modCount 변환 여부를 체크하여 에러가 발생한다
checkForComodification()에서 modCount가 변한 여부를 체크한다
unmodifiableList
로 감싸자@Test
void enhancedForLoop(){
ExecutorService executor = Executors.newFixedThreadPool(4);
List<String> copyTarget = new CopyOnWriteArrayList<>(targetList);
for (String str : copyTarget) {
executor.submit(() -> {
if (str.equals("a")) {
synchronized (copyTarget) {
copyTarget.remove(str);
}
}
});
}
executor.shutdown(); // 새로운 작업을 받아들이지 않고, 이미 실행된 작업들을 모두 실행한 후 종료
while (!executor.isTerminated()) {
// 모든 작업이 끝날 때까지 기다림
}
assertThat(copyTarget.size()).isEqualTo(300000);
}
@Test
void enhancedUnmodifiableForLoop() {
long startTime = System.currentTimeMillis();
List<String> unmodifiableList = Collections.unmodifiableList(targetList);
List<String> afterFilter = unmodifiableList.parallelStream().filter(str -> {
// System.out.println(Thread.currentThread().getName() + " processed " + str);
if(str.equals("a")){
return false;
}
return true;
}).toList();
assertThat(afterFilter.size()).isEqualTo(300000);
long endTime = System.currentTimeMillis();
System.out.println("Execution time: " + (endTime - startTime) + " ms");
}
iterator의 remove() 로 처리한 결과는 2993ms 였고
parallelStream으로 처리한 결과 71ms 로
결과적으로 97% 성능을 향상시켰다