You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
여기서는 멀티스레드를 사용하지 않지만, 스레드1과 스레드2가 동시에 다음 코드를 실행한다고 가정
스레드1: list 에 A 를 추가한다.
스레드2: list 에 B 를 추가한다.
컬렉션에 데이터를 추가하는 add() 메서드를 생각해보면, 단순히 컬렉션에 데이터를 하나 추가하는 것뿐이다. 따라서 이것은 마치 연산이 하나만 있는 원자적인 연산처럼 느껴진다. 원자적인 연산은 쪼갤 수 없기 때문에 멀티스레드 상황에 문제가 되지 않는다.
물론 멀티스레드는 중간에 스레드의 실행 순서가 변경될 수 있으므로 [A, B] 또는, [B, A] 로 데이터의 저장 순서
는 변경될 수 있지만, 결과적으로 데이터는 모두 안전하게 저장될 것 같다. 하지만 컬렉션 프레임워크가 제공하는 대부분의 연산은 원자적인 연산이 아니다.
이 메서드는 단순히 데이터 하나를 추가하는 기능을 제공한다. 따라서 밖에서 보면 원자적인 것처럼 보인다. 이 메서드는 단순히 데이터를 추가하는 것으로 끝나지 않는다. 내부에 있는 배열에 데이터를 추가해야 하고, size 도 함께 하나 증가시켜야 한다. 심지어 size++ 연산 자체도 원자적이지 않다. size++ 연산은 size = size + 1연산과 같다.
이렇게 원자적이지 않은 연산을 멀티스레드 상황에 안전하게 사용하려면 synchronized , Lock 등을 사용해서 동기화를 해야한다.
스레드1, 스레드2가 elementData[size] = e 코드를 동시에 수행한다. 여기서는 스레드1이 약간 빠르게 수행했다.
스레드1 수행: elementData[0] = A , elementData[0] 의 값은 A가 된다.
스레드2 수행: elementData[0] = B , elementData[0] 의 값은 A B가 된다.
결과적으로 elementData[0] 의 값은 B가 된다.
과정 2
스레드1, 스레드2가 sleep() 에서 잠시 대기한다. 여기서 sleep() 을 사용한 이유는 동시성 문제를 쉽게 확인하기
위해서다.
이 코드를 제거하면 size++ 이 너무 빨리 호출되기 때문에, 스레드1이 add() 메서드를 완전히 수행하고 나서 스레
드2가 add() 메서드를 수행할 가능성이 높다.
당연한 이야기지만 sleep() 코드를 제거해도 멀티스레드 동시성 문제는 여전히 발생할 수 있다. (확률의 차이이다.)
예를 들어서 sleep() 코드를 제거해도 다음과 같은 상황이 발생할 수 있다.
과정 3
상황 1
스레드1, 스레드2가 size++ 코드를 동시에 수행한다. 여기서는 스레드1이 약간 빠르게 수행했다.
스레드1 수행: size++ , size 의 값은 1이 된다.
스레드2 수행: size++ , size 의 값은 1 2가 된다.
결과적으로 size 의 값은 2이 된다.
상황 2
스레드1, 스레드2가 size++ 코드를 동시에 수행한다. 여기서는 스레드1, 스레드2가 거의 동시에 실행되었다.
스레드1 수행: size = size + 1 연산이다. size 의 값을 읽는다. 0이다.
스레드2 수행: size = size + 1 연산이다. size 의 값을 읽는다. 0이다.
스레드1 수행: size = 0 + 1 연산을 수행한다.
스레드2 수행: size = 0 + 1 연산을 수행한다.
스레드1 수행: size = 1 대입을 수행한다.
스레드2 수행: size = 1 대입을 수행한다.
결과적으로 size 의 값은 1이 된다.
우리가 본 케이스는 상황1이지만, size++ 연산도 원자적인 연산이 아니므로 때때로 상황2가 될 수도 있다. (따라서 로그에서 size 값이 1로 출력될 가능성도 있다.)
컬렉션 프레임워크 대부분은 스레드 세이프 하지 않다.
우리가 일반적으로 자주 사용하는 ArrayList , LinkedList , HashSet , HashMap 등 수 많은 자료 구조들은 단순한 연산을 제공하는 것 처럼 보인다. 예를 들어서 데이터를 추가하는 add() 와 같은 연산은 마치 원자적인 연산처럼 느껴진다.
하지만 그 내부에서는 수 많은 연산들이 함께 사용된다. 배열에 데이터를 추가하고, 사이즈를 변경하고, 배열을 새로 만들어서 배열의 크기도 늘리고, 노드를 만들어서 링크에 연결하는 등 수 많은 복잡한 연산이 함께 사용된다. 따라서 일반적인 컬렉션들은 절대로! 스레드 세이프 하지 않다!
단일 스레드가 컬렉션에 접근하는 경우라면 아무런 문제가 없지만, 멀티스레드 상황에서 여러 스레드가 동시에 컬렉션에 접근하는 경우라면 java.util 패키지가 제공하는 일반적인 컬렉션들은 사용하면 안된다! (물론 일부 예외도 있
다. 뒤에서 설명한다.)
최악의 경우 실무에서 두 명의 사용자가 동시에 컬렉션에 데이터를 보관했는데, 코드에 아무런 문제가 없어 보이는데, 한명의 사용자 데이터가 사라질 수 있다.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
동시성 컬렉션이 필요한 이유1 - 시작
참고로 여러 스레드가 동시에 접근해도 괜찮은 경우를 스레드 세이프(Thread Safe)하다고 한다.그렇다면 ArrayList 는 스레드 세이프 할까?
실행 결과
여기서는 멀티스레드를 사용하지 않지만, 스레드1과 스레드2가 동시에 다음 코드를 실행한다고 가정
컬렉션에 데이터를 추가하는 add() 메서드를 생각해보면, 단순히 컬렉션에 데이터를 하나 추가하는 것뿐이다. 따라서 이것은 마치 연산이 하나만 있는 원자적인 연산처럼 느껴진다. 원자적인 연산은 쪼갤 수 없기 때문에 멀티스레드 상황에 문제가 되지 않는다.
물론 멀티스레드는 중간에 스레드의 실행 순서가 변경될 수 있으므로 [A, B] 또는, [B, A] 로 데이터의 저장 순서
는 변경될 수 있지만, 결과적으로 데이터는 모두 안전하게 저장될 것 같다. 하지만 컬렉션 프레임워크가 제공하는 대부분의 연산은 원자적인 연산이 아니다.
컬렉션 직접 만들기
실행 결과
동시성 컬렉션이 필요한 이유2 - 동시성 문제
멀티스레드 문제 확인
이 메서드는 단순히 데이터 하나를 추가하는 기능을 제공한다. 따라서 밖에서 보면 원자적인 것처럼 보인다. 이 메서드는 단순히 데이터를 추가하는 것으로 끝나지 않는다. 내부에 있는 배열에 데이터를 추가해야 하고, size 도 함께 하나 증가시켜야 한다. 심지어 size++ 연산 자체도 원자적이지 않다. size++ 연산은 size = size + 1연산과 같다.
이렇게 원자적이지 않은 연산을 멀티스레드 상황에 안전하게 사용하려면 synchronized , Lock 등을 사용해서 동기화를 해야한다.
실행 결과
과정 1
스레드1, 스레드2가 elementData[size] = e 코드를 동시에 수행한다. 여기서는 스레드1이 약간 빠르게 수행했다.
결과적으로 elementData[0] 의 값은 B가 된다.
과정 2
위해서다.
드2가 add() 메서드를 수행할 가능성이 높다.
예를 들어서 sleep() 코드를 제거해도 다음과 같은 상황이 발생할 수 있다.
과정 3
상황 1
스레드1, 스레드2가 size++ 코드를 동시에 수행한다. 여기서는 스레드1이 약간 빠르게 수행했다.
결과적으로 size 의 값은 2이 된다.
상황 2
스레드1, 스레드2가 size++ 코드를 동시에 수행한다. 여기서는 스레드1, 스레드2가 거의 동시에 실행되었다.
스레드1 수행: size = size + 1 연산이다. size 의 값을 읽는다. 0이다.
스레드2 수행: size = size + 1 연산이다. size 의 값을 읽는다. 0이다.
스레드1 수행: size = 0 + 1 연산을 수행한다.
스레드2 수행: size = 0 + 1 연산을 수행한다.
스레드1 수행: size = 1 대입을 수행한다.
스레드2 수행: size = 1 대입을 수행한다.
결과적으로 size 의 값은 1이 된다.
우리가 본 케이스는 상황1이지만, size++ 연산도 원자적인 연산이 아니므로 때때로 상황2가 될 수도 있다. (따라서 로그에서 size 값이 1로 출력될 가능성도 있다.)
컬렉션 프레임워크 대부분은 스레드 세이프 하지 않다.
우리가 일반적으로 자주 사용하는 ArrayList , LinkedList , HashSet , HashMap 등 수 많은 자료 구조들은 단순한 연산을 제공하는 것 처럼 보인다. 예를 들어서 데이터를 추가하는 add() 와 같은 연산은 마치 원자적인 연산처럼 느껴진다.
하지만 그 내부에서는 수 많은 연산들이 함께 사용된다. 배열에 데이터를 추가하고, 사이즈를 변경하고, 배열을 새로 만들어서 배열의 크기도 늘리고, 노드를 만들어서 링크에 연결하는 등 수 많은 복잡한 연산이 함께 사용된다. 따라서 일반적인 컬렉션들은 절대로! 스레드 세이프 하지 않다!
단일 스레드가 컬렉션에 접근하는 경우라면 아무런 문제가 없지만, 멀티스레드 상황에서 여러 스레드가 동시에 컬렉션에 접근하는 경우라면 java.util 패키지가 제공하는 일반적인 컬렉션들은 사용하면 안된다! (물론 일부 예외도 있
다. 뒤에서 설명한다.)
최악의 경우 실무에서 두 명의 사용자가 동시에 컬렉션에 데이터를 보관했는데, 코드에 아무런 문제가 없어 보이는데, 한명의 사용자 데이터가 사라질 수 있다.
그럼 어떻게 해야할까?
Beta Was this translation helpful? Give feedback.
All reactions