2025-10-24

오늘 배운 것

🧶 Thread-Safe 자료구조


📌 정의

여러 쓰레드가 동시에 접근해도

데이터의 일관성과 안정성이 유지되는 자료구조

즉! 동시에 접근해도 꼬이지 않도록 설계된 구조


🔥 왜 필요한가?

멀티쓰레드 환경에서 일반 자료구조를 쓰면?

List<Integer> list = new ArrayList<>();
// 여러 쓰레드가 동시에 list.add(1); 하면...? 💥 꼬임!
  • 데이터 중복/손실/에러 발생 가능성
  • → 그래서 Thread-Safe한 구조가 필요함!

🧰 대표적인 Thread-Safe 자료구조 (Java 기준)

자료구조 설명
Vector Synchronized ArrayList, 느리지만 안전
Hashtable Synchronized HashMap
ConcurrentHashMap 세그먼트 기반으로 락 분할, 고성능
CopyOnWriteArrayList 변경 시 복사 → 읽기 많은 상황에 최적
ConcurrentLinkedQueue 비차단 큐 (Lock-Free Queue)
BlockingQueue 생산자-소비자 패턴에서 자주 사용
Synchronized Collections Collections.synchronizedList() 등으로 감쌈

⚖️ 방법 1: 전체 락 (Synchronized)

List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
  • 모든 연산에 락이 걸려서 안전하지만 성능 저하 발생
  • 병목 가능성 있음

⚡ 방법 2: 락 분할 / 비차단 (Lock-Free)

Map<String, Integer> map = new ConcurrentHashMap<>();
  • 동시성 높은 상황에 최적화
  • 읽기/쓰기 락 분리, 또는 세그먼트 별 락
  • 성능 & 안정성 둘 다 챙김

🧠 특별한 자료구조

자료구조 특징
AtomicInteger, AtomicLong 락 없이 원자적 연산 가능
ThreadLocal<T> 쓰레드마다 고유한 변수 공간 제공

⛏️ 직접 Thread-Safe하게 만들기

  • Synchronized method
  • ReentrantLock 사용
  • volatile 키워드 활용 (읽기 일관성 보장)

💬 면접 포인트 질문

질문 요약 답변
Thread-Safe란? 여러 쓰레드에서 동시 접근해도 데이터가 꼬이지 않음
왜 일반 List는 위험함? 동시에 add/remove 시 race condition 발생 가능
ConcurrentHashMap의 장점? 성능 좋은 동시성 Map (락 분할 구조)
Synchronized와 Lock 차이? Synchronized는 간단, Lock은 정교함과 제어 가능
읽기 많은 환경에서는? CopyOnWriteArrayList 등 읽기 전용 구조 사용

✅ Personal Recommendation

  • ArrayList vs Vector 동시 접근 실험
  • ConcurrentHashMap 성능 측정 실험
  • 직접 락 걸어서 Thread-Safe 큐 만들어보기
  • Thread Safe 한 자료구조가 있을까요? 없다면, 어떻게 Thread Safe 하게 구성할 수 있을까요?

    네 존재합니다

    자바의 경우, ConcurrentHashMap, ConcurrentLinkedQueue, CopyOnWriteArrayList같은 동시성을 지원하는 자료구조가 이미 제공되고 있습니다

    이러한 자료구조들은 내부적으로 락이나 세그먼트 분할같은 기법을 사용해, 여러 스레드가 동시에 접근해도 데이터의 일관성과 무결성이 깨지지 않도록 설계되어있습니다.

    만약 사용하는 자료구조가 기본적으로 Thread-Safe하지 않다면, Thread-Safe하게 만들기 위해서는 다음과 같은 방법을 사용할 수 있습니다.

    첫째는 명시적인 락을 걸어 여러 스레드가 동시에 읽거나 쓸 때 충돌을 방지하는 방법입니다.

    둘째는 atomic 연산을 이용해 간단한 데이터 변경을 락 없이 안전하게 처리하는 방법입니다.

    마지막으로 세번째는 copy-on-write 전략 처럼 수정이 필요할 때마다 데이터 전체를 복사해서 변경하는 방법도 있습니다. 이 경우 읽기 성능은 좋지만, 쓰기 비용이 높습니다.

    따라서 상황에 따라 내장된 Concurrent자료구조를 활용하거나, 별도 락이나 원자성 보장을 추가해 Thread-Safe하게 구성할 수 있습니다.

  • 배열의 길이를 알고 있다면, 조금 더 빠른 Thread Safe한 연산을 만들 순 없을까요?

    네 배열의 길이를 알고있다면 ThreadSafe한 연산을 만들 수 있습니다

    일반적으로 다중 스레드 환경에서 배열을 사용할 때, 충돌을 막기 위해 전체 배열에 락을 걸거나 배열 원소 하나하나에 락을 걸 수 있습니다.

    하지만 배열 크기가 고정되어 있다는 사실을 활용하면, 다음과 같은 방법으로 락 경합을 줄일 수 있습니다.

    첫째는 인덱스 기반 샤딩 처리를 할 수 있습니다

    배열 크기가 고정되어 있다면, 각 스레드가 접근할 배열 인덱스를 미리 할당하거나, 특정 해시나 인덱스 모듈 연산을 이용해 스레드끼리 충돌을 최소화 할 수 있습니다.

    두번재는 atomic배열을 사용할 수 있습니다.

    자바에서는 AtomicIntegerArray, AtomicLongArray같은 자료구조가 존재하는데, 각 배열 원소에 대한 CAS(Compare-And-Swqp) 기반의 원자 연산을 수행할 수 있어 락없이 빠르고 안전한 접근이 가능합니다.

    이처럼 배열 길이를 알고 있고 배열이 고정되어 있다면, 별도의 무거운 락 없이 인덱스 단위의 샤딩 처리나 아토믹 연산만으로 스레드 세이프한 구조를 만들 수 있습니다

    • cas는 뭔가요?

      cas는 다중 스레드 환경에서 락 없이 데이터의 일관성을 보장하기 위해 사용되는 원자적 연산입니다.

      cas는 메모리에 저장된 값이 예상한 값과 같은면 새로운 값으로 교체하고, 다르면 아무것도 하지 않는 방식으로 동작합니다.

      즉 현재 값이 기대하는 값이면 업데이트 라는 조건부 교체를 통해 여러 스레드가 동시에 값을 수정하려 할 때 충돌 없이 안전하게 변경할 수 있게 해줍니다.

      이 과정은 cpu레벨의 특수 명령어를 통해 하나의 원자적 동작으로 보장되기 때문에, 별도의 락을 사용하지 않고도 스레드 세이프를 유지할 수 있습니다.

      다만 충돌이 빈번하게 발생하는 경우에는 cas 실패 후 재시도 하는 비용이 발생할 수 있어, 상황에 따라 주의가 필요합니다

  • 사용하고 있는 언어의 자료구조는 Thread Safe한가요? 그렇지 않다면 ThreadSafe한 Wrapped Data Structure를 제공하고 있나요?

    자바에서 기본적으로 제공하는 컬렉션 자료구조들은 대부분 스레드 세이프 하지 않습니다.

    예를들어 arrayList, hashMap, hashSet, LinkedList 등은 다중 스레드 환경에서 동시에 접근하면 데이터 불일치, ConcurrentModificationException같은 문제가 발생할 수 있습니다.

    대신 자바는 스레드 세이프한 버전의 자료구조를 만들 수 있도록 다음과 같은 방법들을 제공합니다

    첫째는 Collections.synchronizedXXX() 메서드를 사용해 기존 컬렉션을 동기화된 래퍼로 감쌀 수 있습니다

    둘째는 Concurrent패키지를 사용해 아예 다중 스레드를 고려해 설계된 자료구조를 제공합니다.

    대표적으로 ConcurrentHashMap, CopyOnWriteArrayList. ConcurrentLinkedQueue등이 있습니다

    따라서 기본 자료구조는 스레드 세이프 하지 않지만 필요한 경우 동기화된 래퍼를 적용하거나 Concurrent 전용 자료구조를 사용하는 방식으로 스레드 세이프한 환경을 구성할 수 있습니다.

results matching ""

    No results matching ""