나누고 싶은 개발 이야기

Data Engineer로서 기록하고 공유하고 싶은 기술들. 책과 함께 이야기합니다.

Language/Java

[multi thread] java.util.concurrent Part 1

devidea 2019. 1. 4. 18:39
Java에서는 멀티스레드 프로그램을 작성하는데 도움이 되는 많은 클래스들을 구현해 놓았다. java.util.concurrent 패키지 안에 포함되어 있는데 패키지 내용을 잘 설명한 IBM의 문서가 있어서 이해한 만큼 한글로 정리해 보고자 한다.

1. TimeUnit
TimeUnit는 class가 아닌 Enum 타입이다. TimeUnit을 사용하면 코드를 읽기 쉽도록 만들어 준다. Java 개발자가 millisecond 단위의 숫자로 코드를 지저분하게 하는 대신 사용하면 좋다. MILLISECONDS, MICROSECONDS에서부터 DAY, HOURS에 이르기까지 개발자가 다루고자 하는 대부분의 범위의 시간을 표현할 수 있다. 또한 enum에 구현된 함수를 통해 시간 단위 변환을 쉽게 해준다.


2. CopyOnWriteArrayList
시간과 메모리 사용 관점에서 배열의 새로운 복사본을 만드는 것은 비싼 작업이다. 그래서 개발자들을 동기화된 ArrayList를 자주 사용합니다. 하지만 이 역시도 비싼 작업이다. 동시성을 보장하기 위해서 collection의 모든 요소들을 순회하면서 일어나는 읽기/쓰기 작업을 모두 동기화 해야하기 때문이다.

많은 사용자가 collection의 데이터를 읽고 일부 데이터를 수정한다고 가정하자.
CopyOnWriteArrayList는 이런 상황을 풀 수 있는 좋은 방법이다. Javadoc에 다음과 같이 기술되어 있다.
"A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array."

Collection에 변화가 일어나는 작업을 할 때, 새로운 복사본을 스레드 안전성을 유지해서 만들어준다. Collection의 데이터를 읽는 사용자는 동기화 비용이 발생하지 않는다 (데이터 변경을 하지 않기 때문에).

CopyOnWriteArrayList는 자주 읽고 드물에 변경하는 경우에 사용하면 좋다.


3. BlockingQueue
BlockingQueue는 Queue의 상태를 위해 사용된 interface 이다. Queue는 FIFO (first in, first out) 으로 저장되는 자료구조이다. BlockingQueue에 추가된 기능은 비어있는 Queue에 데이터를 찾으려고 하거나, 꽉 찬 Queue에 데이터를 넣으려고 할 때 호출 스레드를 차단하는 것이다. BlockingQueue를 사용하면 동기화 이슈 없이 한 스레드에서 데이터를 넣고 다른 스레드에서 데이터를 처리할 수 있다. Java Tutorial의 Guarded Blocks 예제가 있는데 BlockingQueue를 사용하면 동일 기능을 보다 쉽게 구현할 수 있다. Guarded Blocks 예제에서는 데이터 동기화를 위해 wait(), notifyAll() 함수로 스레드간 시그널을 보내는 방식으로 구현했다.
아래는 ArrayBlockingQueue로 구현된 샘플이다. wait()/ notifyAll()을 볼 수 없고, 스레드 안전하게 구현된 put(), take()을 사용하고 있다.


4. ConcurrentMap
Map은 동시성과 관련하여 미묘한 버그를 가지고 있어 사용할 때 부주의하기 쉽다.
여러 스레드가 Map을 사용하는 환경에서 Map의 key/value를 변경하기 전에 containsKey() 또는 get()으로 값을 가져온다. 동기화된 Map을 사용하더라도 처리 중에 Map의 제어권을 몰래 가져올 수 있다. lock을 통해 get() 함수를 실행해 값을 가져오다라도 put()을 하기 전에 lock을 풀게 되면 그 사이 다른 스레드가 Map의 값을 수정할 수 있다. 이를 경쟁조건에 놓였다고 하는데, 여러 스레드가 같은 Map에 경쟁을 함으로서 미묘한 시간 차이 때문에 처음과 다른 결과값이 도출될 수 있다.

이를 해결하기 위해 ConcurrentMap은 putIfAbsent()와 같이 하나의 lock으로 값을 얻고 수정될 때까지 단일연산을 보장하는 함수들을 추가했다.


5. SynchronousQueues
SynchronousQueues는 queue에 데이터를 넣기 위해서는 반드시 데이터를 받고자 하는 다른 스레드가 존재해야 한다. 다르게 표현하면 SynchronousQueues는 내부에 데이터 저장 공간이 없다. SynchronousQueues는 BlockingQueue의 다른 구현버전이다. 이것은 극단적으로 한 데이터를 하나의 스레드에서 다른 스레드로 보내도록 경량화해 구현했다. SynchronousQueues를 사용하는 방법은 ArrayBlockingQueue와 다르지 않다.


참고 문서


반응형

'Language > Java' 카테고리의 다른 글

[Java9] CompletableFuture 지연 & 시간초과 개선  (0) 2019.01.28
[Java8] sorted groupBy  (0) 2019.01.11
Dynamic Proxies  (0) 2017.08.29
[Java8] 람다란 무엇인가?  (0) 2017.08.09
[Java8] CompletableFuture 정리  (0) 2017.08.09