새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.

4 분 소요

synchronized를 사용하는 것은 무조건 좋지 못하고 성능 저하를 발생시킨다고 생각을 했습니다.

그런데 ConcurrentHashMap 내부 로직을 보면 여전히 synchronized를 사용하게 됩니다.

synchronized는 간단하게 데이터 일관성과 동시성 문제를 해결할 수 있는 자바 키워드 입니다.

조금 더 유연하게 데이터 일관성과 유연한 코드 작성을 도와주는 ReentrantLock을 사용할 수 있으며,

단일 변수의 원자 연산을 Lock을 사용하지 않고 CAS 알고리즘으로 동시성을 제어할 수 있습니다.

synchronized 5W1H

1차 시도(피드백 전)

# what 정의

synchronized는 공유 자원을 접근하는 코드 영역을 상호배제를 통해 동시성 문제를 해결하는 방식입니다.

# why

synchronized를 사용하는 이유는 1. 명확성 2.자바 기본 제공으로 동시성 문제를 간단하게 해결할 수 있습니다.

# how

객체 코드는 mark부분에 객체 접근 제어 상태를 저장하는 공간이 있습니다.

이를 통해 cas나 운영체제의 mutex를 사용하여 여러 스레드가 동시에 해당 객체나 메서드 영역을 접근할 때 상태 값을 통해

접근을 제어하는 방식입니다.

# where

클래스나 메서드 레벨에서 사용할 수 있습니다.

# when

동시성 문제가 거의 발생하지 않는 경우

하나의 스레드가 주로 사용하는데 동시성을 예방하고 싶은 경우에 사용합니다.

# who

공유 자원에 접근하는 경우에 사용합니다.

피드백(GPT-4o)

# What

synchronized는 공유 자원을 접근하는 코드 영역을 상호배제를 통해 동시성 문제를 해결하는 방식입니다.

synchronized는 JVM 레벨에서 관리되는 동기화 메커니즘으로 공유 자원에 대한 코드 블록 또는 메서드의 동시 접근을 상호배제를 통해 제어하는 동기화 수단입니다.

JVM 레벨에서 관리되는 동기화 메커니즘이 추가되었다.

synchronized는 JVM 프로세스( 하나의 자바 애플리케이션 인스턴스) 안에서만 동기화를 보장합니다.
서로 다른 JVM 프로세스 간에는 synchronized는 동기화를 보장하지 못합니다.

분산 서버에서의 한계가 발생합니다. 서버 이중화나 MSA와 같은 분산 서버 환경에서는 synchronized만으로는 공유 자원에 대한 동시성 문제를 해결할 수 없습니다.

별도의 분산 서버 동기화 매커니즘이 필요하게 됩니다.

# why

synchronized를 사용하는 이유는 1. 명확성 2.자바 기본 제공으로 동시성 문제를 간단하게 해결할 수 있습니다.

synchronized는 자바에서 기본 제공되며, 구현이 간단하고 경합이 적은 상황에서 성능 손실 없이 동시성 문제를 안전하게 해결할 수 있습니다.

  • 데이터 일관성 보장
  • 경쟁 조건 방지: 여러 스레드가 동시에 공유 자원에 접근하려 할 때, 접근 순서에 따라 결과가 달라지는 상황을 막습니다.

# how

객체 코드는 mark부분에 객체 접근 제어 상태를 저장하는 공간이 있습니다.

이를 통해 cas나 운영체제의 mutex를 사용하여 여러 스레드가 동시에 해당 객체나 메서드 영역을 접근할 때 상태 값을 통해

접근을 제어하는 방식입니다.

JVM은 synchronized 키워드를 만나면 객체의 Mark word 영역을 통해 락 상태를 확인하고, 락 경합 수준에 따라 CAS, 경량 락, 편향 락 또는 운영체제의 mutex로 처리하여 접근을 제어합니다.

# where

synchronized는 인스턴스 메서드, static 메서드, 또는 특정 코드 블록 내에서 사용할 수 있으며, 각 위치에 따라 락의 범위가 달라집니다.

# when: 사용 시점

동시성 문제가 거의 발생하지 않는 경우 / 하나의 스레드가 주로 사용하는데 예방하고 싶은 경우

동시성 문제가 거의 발생하지 않거나 하나의 스레드가 주로 사용하는 경우에는 synchronized 키워드를 사용할 필요가 없습니다.

오히려 성능 저하를 발생시킬 수 있습니다.

동시성 문제가 발생할 가능성이 있거나 발생할 경우 심각한 데이터 손상이 우려될 때 사용하는 것이 맞습니다.

  • 여러 스레드가 공유 자원에 동시에 접근하여 데이터 일관성이 깨질 우려가 있을 때 사용합니다.
  • 원자적인(Atomic) 연산이 보장되어야 할 때(카운터 증가 등) 사용됩니다.

# who 사용 대상

공유 자원을 여러 스레드가 동시에 접근하거나 수정할 가능성이 있는 경우에 사용합니다.

when에 대한 생각

동시성 문제가 거의 발생하지 않는 경우 / 하나의 스레드가 주로 사용하는데 예방하고 싶은 경우

위와 같이 대답을 한 이유와 대답이 올바르지 않다고 피드백을 받은 이유를 정리합니다.

동시성 문제가 거의 발생하지 않는 경우 하나의 스레드가 주로 사용하는데 동시성을 예방하고 싶은 경우에 사용합니다.

synchronized는 3개 방식으로 동시성을 제어합니다.

편향 락, 가벼운 락, 무거운 락 3가지 방식이 있는데

무거운 락 방식은 운영체제의 시스템 콜을 사용하여 자원 제어를 하기에 컨택스트 스위칭이 발생하여 성능저하가 발생하게 됩니다.

다만 하나의 스레드가 주로 사용하거나 동시성 문제가 거의 발생하지 않은 경우에는 가벼운 락이나 편향락으로 처리되기에 when 부분을 작성하게된 배경입니다.

혼란을 주는 표현

  1. 동시성 문제가 거의 발생하지 않는 경우

    이 문장만 보면 오히려 ‘락이 필요없는 상황으로 느껴짐’

    경합 가능성은 낮지만, 예상치 못한 상황에서 동시성 문제가 발생할 수 있는 경우에는 synchronized를 통해 예방적 동기화를 적용할 수 있습니다.

정리

락 종류 사용 조건 내부 동작
편향 락 (Biased Lock) 객체에 하나의 스레드만 반복 접근하고, 경합이 거의 없음 객체 헤더에 스레드 ID를 기록하여 락 체크 자체를 생략 (사실상 락 없는 상태)
경량 락 (Lightweight Lock) 다른 스레드가 락에 진입 시도할 때 객체의 Mark Word를 CAS로 갱신하며 스핀 방식으로 락 획득 시도
중량 락 (Heavyweight Lock) 경합이 심해서 경량 락 획득 실패 시 OS 수준의 monitor mutex 사용 + 스레드 block/wait 발생 + 컨텍스트 스위칭 비용 발생
  1. A 스레드가 객체에 진입 → 편향 락 설정됨 (Mark Word에 A의 ID 기록)
  2. B 스레드가 진입 시도 → 편향 락 해제 → 경량 락으로 전환
  3. A, B가 동시에 CAS로 락 시도 → CAS 실패 반복중량 락 전환

synchronized는 언제 사용해야 할까?

공유 자원에 대한 여러 스레드의 동시 접근으로 발생할 수 있는 데이터 일관성 문제(경쟁 조건)를 방지하기 위해서입니다.

내부적에서 편향 락(Biased Locking) → 경량 락(Lightweight Locking) → 중량 락(Heavyweight Locking) 순으로 단계적으로 전환되며 작동합니다. 락 경합이 거의 없는 상황에서는 편향 락이나 경량 락 수준에서 처리되므로 성능 부담이 거의 없습니다.

따라서, 데이터 일관성 보장이 필수적이지만 락 경합이 낮은 시나리오 에서는 synchronized를 사용하는 것이 효율적일 수 있습니다.

주로 하나의 스레드가 해당 자원에 접근하거나, 예상치 못한 동시성 문제를 예방하는 차원에서 간결하게 사용할 수 있습니다.

복잡한 락 프리 알고리즘이나 외부 동기화 메커니즘을 도입하는 오버엔지니어링을 방지할 수 있습니다.

ConcurrentHashMap와 synchronized의 활용

1차 작성

ConcurrentHashMap의 내부 코드를 보면 synchronized 키워드를 사용합니다.

동시성 문제를 CAS 알고리즘을 기반으로 락프리로 구현하여 락으로 인한 성능 저하를 방지할 수 있습니다.

다만 수정하는 것과 상관없는 데이터를 추가하거나 compareIfAbent와 같이 데이터를 생성할 때, 내부 해시 충동로 list-> tree구조로 변경되는 경우등

이런 경우에는 내부에서 데이터 일관성을 보장하기 위해 구현이 간단하고 최적화가된 synchronized를 사용하여 동시성을 제어합니다.

피드백

ConcurrentHashMap은 높은 동시성을 위해 대부분 연산을 CAS 알고리즘을 기반으로 락 프리로 구현하였으며, 락으로 인한 성능 저하를 최소화합니다.

ConcurrentHashMap는 내부적으로 synchronized를 사용하여 데이터 일관성을 보장하고 구현의 복잡성을 줄입니다.

  1. 초기화 및 구조 변경: 특정 해시 버킷의 연결 리스트가 너무 길어져 성능 저하가 예상될 때, 이를 트리 구조로 변경하는 과정에 사용됩니다. 복잡한 구조 변경 작업을 안전하게 수행할 수 있습니다.
  2. 노드 추가 시 특별한 경우: put putIfAbsent와 같이 새로운 데이터를 추가하는 과정에서 특정 버킷이 아직 초기화되지 않거나 복잡한 충돌해소가 필요할 때 사용됩니다.

synchronized는 구현의 복잡도를 줄이고, 해당 작업이 빈번하지 않기 때문에 발생하는 성능 오버헤드가 미미합니다.

JVM의 최적화(편향락, 경량 락) 덕분에 효율적으로 동작하기 때문입니다. 각 상황에 맞는 최적의 동시성 제어방식을 선택하게 된 배경입니다.

댓글남기기