Java Adv 17 - 동시성 컬렉션이 필요한 이유 - 2

멀티스레드 환경에서의 문제 확인

  • add() - 원자적이지 않은 연산
public void add(Object e) {
elementData[size] = e;
sleep(100); // 멀티스레드 문제를 쉽게 확인하는 코드
size++;
}

실제 멀티스레드 환경에서 사용

image image

테스트 코드를 간단하게 작성해서 각각의 Thread가 A와 B를 list에 저장하게끔 하였다. 원자적 연산을 지원하지는 않겠지만 어떤 결과를 확인해 볼 수 있을까?

image

스레드1 수행: elementData[0] = A , elementData[0] 의 값은 A가 된다. 스레드2 수행: elementData[0] = B , elementData[0] 의 값은 A → B가 된다.

이 말이 무슨말이냐면 스레드1이 elementData[0]에 A를 저장하고, 동시에 B도 0에다가 저장을 하기 때문에 인덱스 0번에 A가 저장되었다가 B로 바뀌는 것을 말한다. 그리고 size++을 보면 size++은 size = size + 1이다 Thread-1이 size++; 연산을 실행하면 1이 저장이 되어있고, 그 뒤에 thread-2가 1을 더하면 2가 저장이 되는데 이 밖의 상황도 발생할 수가 있다. thread-1이 1을 증가시킬 때 동시에 thread-2도 같은 연산을 실행해서 size의 저장되어 있는 값이 0이었기 때문에 1로 증가시키는 연산을 진행하면 2가 아닌 1이 출력될 수 있다. 이렇게 되버리면 데이터의 정합성이 모두 깨진다.

컬렉션 프레임워크 대부분은 스레드 세이프 하지 않다.

일반적으로 자주 사용하는 ArrayList , LinkedList , HashSet , HashMap 등 수 많은 자료 구조들은 단순한 연산을 제공하는 것 처럼 보인다. 예를 들어서 데이터를 추가하는 add() 와 같은 연산은 마치 원자적인 연산처럼 느껴진다. 하지만 그 내부에서는 수 많은 연산들이 함께 사용된다. 배열에 데이터를 추가하고, 사이즈를 변경하고, 배열을 새로 만들어서 배열의 크기도 늘리고, 노드를 만들어서 링크에 연결하는 등 수 많은 복잡한 연산이 함께 사용된다. 따라서 일반적인 컬렉션들은 절대로! 스레드 세이프 하지 않다! 단일 스레드가 컬렉션에 접근하는 경우라면 아무런 문제가 없지만, 멀티스레드 상황에서 여러 스레드가 동시에 컬렉션에 접근하는 경우라면 java.util 패키지가 제공하는 일반적인 컬렉션들은 사용하면 안된다! 최악의 경우 실무에서 두 명의 사용자가 동시에 컬렉션에 데이터를 보관했는데, 코드에 아무런 문제가 없어 보이는데, 한명의 사용자 데이터가 사라질 수 있다.

아주 간단한 동시성 해결

image

사실 synchronized라는 키워드 하나를 걸어주면 동시성이 해결된다. 멀티스레드 환경에서 안전하게 사용이 가능하다.

image

구현체를 SyncList로 갈아껴주었다.

image

안전한 임계영역을 형성해주면 컬렉션 프레임워크도 멀티스레드 환경에서 안전하게 사용할 수 있다.

Reference

김영한님의 자바 강의



© 2022. by taewoo

Powered by taewoo