TIL

ConcurrentHashMap은 어떤 방식으로 동시성 문제를 해결할까?

삼공비 2024. 6. 16. 17:57

ConcurrentHashMap

  • 동시성에 최적화된 해시맵 구현체
  • 여러 스레드가 동시에 데이터를 읽고 쓸 수 있도록 설계되어 있다
  • 동시성 최적화 방식
    • 세그먼트 락킹
      • Java8 이전
        • 해시맵이 여러 세그먼트로 나누어져 각 세그먼트마다 개별적인 락을 사용했다
        • 하나의 세그먼트에서만 락이 걸려도 다른 세그먼트에 대한 접근이 여전히 가능했다
      • Java8
        • 세그먼트 락킹 대신 세분화된 락으로 변경했다
        • 내부적으로 락 스트라이핑 기법을 사용하여 개별 버킷에 락을 걸지 않고, 필요한 경우에만 부분적으로 락을 사용한다
    • 락 프리 읽기 (Lock-free Reads)
      • 읽기 작업에는 락을 걸지 않고 진행한다
      • 이는 데이터 일관성을 유지하면서도 높은 읽기 성능을 제공한다
      • 읽기 작업이 수행될 때는 데이터 구조의 내부 상태가 변하지 않도록 보장하는 기술을 사용한다
  • 동시성 문제 해결
    • 쓰기 작업에서 충동 최소화하는 방법
      • 여러 스레드가 동시에 데이터를 쓰는 상황에서는 세그먼트 락킹이나 부분적인 락을 사용해서 충돌을 최소화한다
      • 락 프리 읽기와 CAS 연산을 통해 쓰기 작업이 필요한 경우에도 락을 최소화하여 성능 저하를 방지한다
    • 읽기 작업의 높은 성능
      • 읽기 작업이 락 없이 진행되므로, 여러 스레드가 동시에 데이터를 읽어도 성능 저하가 거의 없다
    • 락 경합 감소
      • 락을 세분화하여 사용함으로써, 여러 스레드가 동시에 작업할 때 락 경합이 줄어든다
      • 이는 전통적인 단일 락 기반의 동기화보다 효율적이다
  • 세그먼트 락킹
    • 전체 해시맵을 여러 세그먼트로 나누고, 각 세그먼트에 별도의 락을 적용해서 동시성을 해결한다
  • 세그먼트 락킹 동작 원리
    • 해시맵 분할
      • 여러 세그먼트로 분할하고 각 세그먼트는 독립적인 해시맵처럼 동작하며 자처적인 락을 가진다
      • 데이터는 해시 값을 기반으로 세그먼트에 할당된다
      • 따라서, 서로 다른 세그먼트에 위치한 데이터는 독립적으로 접근할 수 있다
    • 세그먼트 락
      • 각 세그먼트는 별도의 락을 가지고 있어서 다른 세그먼트에는 여향을 미치지 않는다
      • 스레드가 데이터를 읽거나 쓸 때 해당 데이터가 속한 세그먼트 락만을 획득한다
      • 이는 락 경합을 줄여주고 성능을 향상한다
    • 읽기 작업
      • 대부분의 읽기 작업은 락을 걸지 않고 수행한다
    • 쓰기 작업
      • 쓰기 작업은 해당 세그먼트의 락을 획득하고 수행한다
      • 쓰기 작업동안 다른 세그먼트는 여전히 락이 걸리지 않으므로 다른 스레가 접근할 수 있다
  • java8 이후 변경 사항
    • 세그먼트 대신, 개별 버킷 수준에서 락을 적용으로 더 세밀한 락 관리가 가능하다
    • 각 버킷에 대해 락을 적용하여 락 경합을 최소화하고 성능을 최적화한다
    • Compare And Swap 연산
      • CAS 연산을 통해 락을 사용하지 않고도 안전하게 값을 업데이트할 수 있다
      • 이는 쓰기 작업에서는 성능을 향상한다
  • 버킷과 세그먼트의 차이점
    • 버킷
      • 해시맵의 하나의 슬롯을 의미한다
      • 해시 함수를 통해 계산된 인덱스에 저장되는 데이터 구조이다
      • 각 버킷은 하나 이상의 키-쌍을 저장할 수 있다
      • 여러 키가 같은 버킷에 저장될 수 있으며, 이를 처리하기 위해 체이닝이나 오픈 어드레싱 등의 충돌 해결 기법을 사용한다
    • 세그먼트
      • 해시맵을 여러 개의 독립적인 부분으로 나눈 것이다
      • 각 세그먼트 독립적인 작은 해시맵처럼 동작하며, 자체적인 락을 가진다
  • 세그먼트는 해시맵을 다시 작은 해시맵으로 나누어서 동시성을 해결하는 방식인 거 같다
  • 버킷은 해시맵에 저장되는 기본 단위인 버킷 레벨에서 락을 관리하는 방법인 거 같다
  • 세그먼트에서 버킷 방식으로 변경된 이유는 버킷으로 락을 관리하면 단위가 더 작아서 세그먼트보다 효율적으로 락 경합을 줄일 수 있어서다
  • 그리고 구현도 버킷이 더 간단하고 효율적이다.