[4주차] 동시성

goodstar·2024년 11월 19일
0

Java 스터디

목록 보기
11/14
post-thumbnail

1. 여러 스레드가 모두 한 CPU의 캐시 메모리를 읽으면 가시성 문제가 발생하지 않을까?

( 만약 싱글코어 CPU 라면 ) 가시성 문제는 발생하지 않는다.

가시성 문제는 멀티스레드 환경에서 한 스레드가 값을 변경했을 때, 다른 스레드가 그 변경 사항을 즉시 확인하지 못하는 문제를 말합니다. 따라서 한 CPU의 캐시 메모리를 읽으면 발생하지 않는다.
그러나 동시성 문제는 발생할 수 있다. ( 동시성 문제는 값을 읽고 수정하는 작업이 중단 없이(원자적) 이루어지지 않을 때 발생합니다. )
또, 멀티 코어의 경우 각 코어가 독립적인 캐시 메모리를 가지기 때문에 가시성 문제가 발생할 수 있다.

2. CAS 알고리즘

CAS(Compare-And-Swap)란 : 동시성 문제를 해결하기 위한 비차단(Non-blocking) 알고리즘입니다. 락 없이 원자성을 보장하며, Atomic 클래스에서 이를 내부적으로 활용합니다.

CAS 동작 원리:
1. 메모리에서 현재 값(currentValue)를 읽음.
2. 기대 값(expectedValue)와 현재 값을 비교.
3. 같으면 새로운 값(newValue)으로 업데이트.
4. 다르면 실패하고 다시 시도.

장점: 락을 사용하지 않아 데드락(Deadlock)과 같은 문제가 발생하지 않음. 성능이 뛰어나며, 높은 동시성을 지원.

단점: ABA 문제: 값이 A → B → A로 변경되면, 이를 감지하지 못함.

  • 해결 방법: AtomicStampedReference 사용.

    참조(reference)와 함께 "스탬프(stamp)" 값을 원자적으로 관리하여, 참조 값이 중간에 변경되었다가 원래 값으로 돌아온 상황(ABA)을 감지할 수 있습니다.

스핀락(Spinlock): 반복적으로 실패 시 CPU 자원을 소모

3. Vector, Hashtable, Collections.SynchronizedXXX의 문제점

  • 공통 문제점:
    1. 전체 메서드 락:
      • 모든 메서드에 synchronized가 적용되어 불필요한 락 경합이 발생.
      • 읽기 작업과 쓰기 작업 모두 동일한 락을 사용하므로, 병렬 처리 성능이 저하.
    2. 확장성 부족:
      • 동시성을 요구하는 환경에서 대규모 데이터를 처리하기 어렵습니다. (한 스레드가 락을 점유하면, 다른 모든 스레드가 대기해야 하므로 병렬 작업이 불가능합니다.)
    3. 대기 시간 증가:
      • 여러 스레드가 동시에 접근하려 할 경우 대기 시간이 늘어나며 병목현상이 발생.

해결 방법

  1. Concurrent 컬렉션 사용

    • Vector 대신 CopyOnWriteArrayList 사용
    • Hashtable 대신 ConcurrentHashMap 사용
    • Collections.synchronizedList 대신 병렬 처리에 적합한 컬렉션 사용
  2. 락 범위 최소화

    • 필요한 부분만 락으로 보호하여, 락 경쟁을 줄입니다.
  3. 락 없는 알고리즘 활용

    • Atomic 클래스(AtomicInteger, AtomicReference)를 활용하거나, 락 없는 자료구조를 사용합니다.

4. SynchronizedList와 CopyOnWriteArrayList의 차이

항목Collections.synchronizedListCopyOnWriteArrayList
동기화 방식synchronized를 통해 전체 락 적용쓰기 시 배열 복사, 읽기 작업은 락 없음
읽기 작업 성능낮음: 락 획득 필요높음: 락 없이 읽기 가능
쓰기 작업 성능중간: 락 사용으로 제한적낮음: 배열 복사로 메모리 사용량 증가
병렬 처리 성능락 경합으로 인해 병렬 처리 성능이 낮음읽기 작업에서는 높은 병렬 처리 성능
사용 환경읽기/쓰기 작업이 모두 빈번한 환경읽기 작업이 빈번하고 쓰기가 드문 환경

Collections.synchronizedList

  1. 동작 방식:
    • 내부적으로 synchronized 키워드를 사용하여 리스트 메서드를 동기화합니다.
    • 단일 락을 사용하여 한 번에 하나의 스레드만 리스트에 접근할 수 있습니다.
  2. 문제점:
    • 모든 메서드 호출에 대해 락을 사용하므로, 스레드 수가 많아질수록 락 경합(Lock Contention)으로 성능이 저하됩니다.
    • 읽기 작업과 쓰기 작업이 동일한 락을 사용하므로 병렬 처리가 불가능합니다.

CopyOnWriteArrayList

  1. 동작 방식:
    • 쓰기 작업(add, remove, set)이 호출될 때마다 배열의 복사본을 생성하여 데이터를 수정합니다.
    • 읽기 작업은 기존 배열(스냅샷)을 참조하므로, 락 없이 동작합니다.
    • 쓰기 작업 후 새로운 배열로 교체하여 일관성을 유지합니다.
  2. 문제점:
    • 쓰기 작업 비용이 매우 높음: 배열을 복사하므로 메모리 사용량 증가 및 성능 저하가 발생합니다.
    • 빈번한 쓰기 작업이 요구되는 환경에는 적합하지 않습니다.

Collections.synchronizedList가 적합한 경우

  • 읽기와 쓰기 작업이 빈번하게 섞여 있는 경우.
  • 리스트의 크기가 작거나, 동시성 수준이 낮은 경우.
  • 스레드 안전성만 필요하며, 성능 요구사항이 크지 않은 경우.

CopyOnWriteArrayList가 적합한 경우

  • 읽기 작업이 매우 빈번하고, 쓰기 작업은 드문 경우.
  • 데이터 크기가 작아 배열 복사 비용이 크지 않은 경우.
  • 동시 읽기 작업의 성능이 중요한 경우.

5. ConcurrentHashMap vs SynchronizedMap

ConcurrentHashMap

  1. 동작 과정:
    • 데이터는 여러 버킷(bucket)으로 나뉘어 저장되며, 각 버킷에 개별적인 락이 적용됩니다.
    • 특정 버킷에만 락을 거므로 병렬 처리가 가능합니다.
  2. 원리:
    • 읽기 연산은 락을 사용하지 않고 진행됩니다.
    • 쓰기 연산은 버킷 단위로 락을 사용하여 동기화합니다.
  3. 결과 및 예시:
    • 다중 스레드 환경에서 읽기와 쓰기 성능이 우수합니다.
    • put() 메서드 호출 시, 특정 키가 포함된 버킷에만 영향을 미칩니다.

SynchronizedMap

  1. 동작 과정:
    • Collections.synchronizedMap() 메서드를 통해 동기화된 맵을 생성합니다.
    • 모든 메서드 호출이 단일 락에 의해 보호됩니다.
  2. 원리:
    • 객체 전체에 대해 락을 거므로, 병렬성이 제한됩니다.
  3. 비교:
    • ConcurrentHashMap은 락 분할 기술로 병렬 처리를 가능하게 하지만, SynchronizedMap은 단일 락으로 인해 스레드가 대기 상태로 전환될 가능성이 높습니다.

0개의 댓글

관련 채용 정보