상세 컨텐츠

본문 제목

[Java] Map 동시성

JAVA

by jeonghojin 2022. 12. 22. 01:22

본문

개인적으로 프로젝트를 진행하던 중 Multi Thread 환경에서 HashMap 사용의 적절성에 대해 정리한다.

 

우선, Multi Thread 환경에서 HashMap은 Thread Safe 하지 않다고 한다.

여러개의 Thread에서 동시에 같은 HashMap에 접근하는 경우, 문제가 발생할 수 있다.

 

더보기

다음과 같은 작업을 수행할 경우 Null 이 반환

(1) HashMap 생성
(2) key를 10, value를 a 로 하여 put() 한다.
(3) 새로운 쓰레드를 생성하여 (1) 에서 생성한 HashMap에 0부터 10까지 반복문을 돌리며 b를 put() 한다.
(4) (1) 에서 put 했던 데이터를 get() 시도
 -> value는 Null 이 반환된다.
> put(10, 'a') 수행
> size를 늘려 새로운 Array를 할당
> put 메서드에서 위 값을 넣을 수 있는 위치 조회 (해시 값이 같아 위치가 같음)

> put(0, 'b') 수행
> size를 늘려 새로운 Array를 할당
> put 메서드에서 (0,'b') 를 넣을 수 있는 위치 조회 (해시 값이 같아 위치가 같음)

> put(1, 'c') 수행
> size를 늘려 새로운 Array를 할당
> put 메서드에서 (1,'b') 를 넣을 수 있는 위치 조회 (해시 값이 같아 위치가 같음)

> put(2, 'b') 수행
> size를 늘려 새로운 Array를 할당
> put 메서드에서 (2,'b') 를 넣을 수 있는 위치 조회 (해시 값이 같아 위치가 같음)

> put(3, 'b') 수행
> size를 늘려 새로운 Array를 할당
> put 메서드에서 (3,'b') 를 넣을 수 있는 위치 조회 (해시 값이 같아 위치가 같음)

> put(4, 'b') 수행
> size를 늘려 새로운 Array를 할당
> put 메서드에서 (4,'b') 를 넣을 수 있는 위치 조회 (해시 값이 같아 위치가 같음)
> ........

> (10,'a') 저장
> (0, 'b') 저장
> (1, 'b') 저장
> (2, 'b') 저장
> (3, 'b') 저장
> (4, 'b') 저장
> .........

put 메서드 호출시, HashMap의 멤버 변수인 table 의 size를 늘리게 된다.

이때, size를 늘리는 과정에서 새로운 Array를 할당하게 된다.

이럴 때, get()을 호출하게 되면 현재 table에는 새로운 Array가 할당되어있고,

이전 table의 데이터를 복사해오기 전이므로 Null 이 반환

 


동시성 이슈 해결 방법


조회, 저장을 하나의 행위로 묶어 반드시 조회와 저장의 트랜잭션이 끝난 뒤에 다른 스레드가 조회와 저장을 하게 하면 동시성 이슈를 제어할 수 있다.
하나로 묶기 위해서는 Lock 이라는 행위를 해야한다.

Lock은 다음 2가지 방식으로 수행할 수 있다.

 

1. 암시적 Lock : syncronized 키워드 사용
 > 메서드, 변수에 사용 가능
 > 기본 타입의 변수 int, long 의 경우엔 Lock 을 사용할 수 없음.

2. 명시적 Lock : ReentrantLock 사용
 > lock() 메서드 호출 시점과 unlock() 메서드 호출 시점 사이의 행위에 대해서 Lock 을 적용할 수 있다.
 

> 아래 코드 참고

 

// 암시적 Lock 예시 코드
static int cnt = 0;

public static void main(String[] args) {
    for (int i = 0; i < 100; i++) {
        new Thread() {
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {
                    System.out.println(add());
                }
            }
        }.start();
    }
}

synchronized public static int add(){
    return ++cnt;
}
// 결과 
999996
999997
999998
999999
1000000


// 명시적 Lock 예시 코드

static int cnt = 0;
static Lock lock = new ReentrantLock();

public static void main(String[] args) {
    for (int i = 0; i < 100; i++) {
        new Thread() {
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {
                    lock.lock();
                    System.out.println(add());
                    lock.unlock();
                }
            }
        }.start();
    }
}

public static int add(){
    return ++cnt;
}


출처 : https://velog.io/@heoseungyeon/Java%EC%97%90%EC%84%9C%EC%9D%98-Multi-Thread-%ED%99%98%EA%B2%BD-%EA%B0%9C%EB%B0%9C

 

HashMap

syncronized 키워드가 존재하지 않는다.

Map 인터페이스 구현체 중 가장 성능이 좋다는 장점은 있지만, syncronized 키워드가 존재하지 않으므로 Multi Thread 환경에서 사용할 수 없다.

HashTable

HashTable 메서드는 전체에 syncronized 키워드를 사용하고 있다.

그렇기 때문에 Multi Thread 환경에서 Thread Safe 하다.

하지만, 동시에 작업을 하는 경우, 객체마다 Lock을 하나씩 가지고 있기 때문에 병목현상이 발생하게 된다.

(HashTable 메서드 접근시 다른 Thread Lock을 얻을 때까지 기다려야 한다.)

Thread-Safe 하지만 속도 면에서 느리다.

ConcurrentHashMap

HashTable 단점을 보완 *JDK 1.5에 검색과 업데이트시 동시성 성능을 높이기 위해서 나왔다고 한다.

Thread-Safe, 속도 ↑

 

 

참고 : 

더보기

'JAVA' 카테고리의 다른 글

[Design Pattern] Template Method Pattern  (0) 2023.03.17
[java] ThreadPool  (1) 2023.02.23
[jwt] Signed Claims JWSs are not supported.  (0) 2022.11.15
[JAVA] Stream - findAny()와 findFirst()  (0) 2022.09.16
재귀함수의 이해  (0) 2022.07.06

관련글 더보기