동시성 프로그래밍이란 ?
여러 작업을 동시에 실행하거나 동시에 실행되는 것처럼 보이게 하는 프로그래밍 방법.
1.동시성과 병렬성의 차이점
동시성은 싱글코어에서도 사용이 가능하며 작업의 효율적인 전환을 통해 논리적으로 여러 작업을 동시에 실행되는 것처럼 보이게하는것이고 병렬성은 멀티코어에서 사용하여 물리적으로 여러 작업을 동시에 처리하여 실행속도를 높이는 기법입니다.
두 속성은 서로 보완적이고 자원의 환경에 따라 적절히 사용해야 합니다.
- 동시성
- 하나의 작업 단위가 번갈아가며 실행되어, 여러 작업이 동시에 실행되는 것 처럼 보이게 만드는 방식
- 단일 코어에서도 가능하며, CPU가 작업을 빠르게 전환(컨텍스트 스위칭) 하여 동시성을 구현합니다
- synchronized는 동시성을 지원할때 사용합니다.
- 락을 사용하는 방식이기 때문에 병렬 실행은 방지됩니다.
- 요리사가 하나의 주방에서 여러 요리를 번갈아 가며 조리
- 병렬성
- 여러 작업이 동시에(물리적) 실행되는 방식.
- 멀티코어 프로세서에서 각 작업이 서로 다른 코어에 병렬적으로 수행됨.(멀티코어 이상의 프로세서 필요)
- 여러 요리사가 각각의 요리를 동시에 조리
2.Thread-Safe하다는 것이 무슨 의미인가요?
멀티스레드 환경에서 여러 스레드가 동시에 접근하더라도 공유 자원의 데이터가 일관성과 무결성이 유지된 상태를 의미합니다.
구현방법
- 동기화(synchronized) 키워드
- 락 인터페이스
- Thread-Safe 클래스
- 컬렉션 (ConcurrentHashMap, CopyOnWriteArrayList)
- 원자적 클래스 (AtomicInteger)
- 불변 객체 사용
3.가시성 문제와 원자성 문제에 대해 설명해 주세요.
가시성이란 물체를 인식하고 식별하는 능력을 말하는데,
가시성 문제는 데이터가 변경되어 적용하는 과정에서 다른 스레드에 즉시 보이지 않는 문제입니다.
원자성 문제는 작업 중간에 간섭받아 값이 잘 못 처리되는 문제입니다.
-
가시성 문제
- 멀티스레드 환경에서 한 스레드가 변경한 값이 다른 스레드에 즉시 보이지 않는 현상.
- 원인
- CPU 캐시와 메인 메모리 간 동기화 지연.
- 명령어 재배치로 인해 값이 최신 상태가 아님.
- 해결 방법
volatile 키워드: 변수의 가시성만 보장 (원자성은 보장하지 않음).
synchronized 키워드: 가시성과 원자성을 모두 보장.
-
원자성 문제
- 작업이 여러 단계로 나뉘어 실행 중, 다른 스레드가 중간 상태에 간섭하여 값이 잘못 처리되는 현상.
- 원인
- 복합 연산(
x++)이 읽기 → 증가 → 쓰기의 단계로 분리됨.
- 여러 스레드가 동일한 자원에 동시에 접근.
- 해결 방법
synchronized 키워드: 락을 사용해 작업 단위를 하나로 묶음.
Atomic 클래스: CAS 알고리즘으로 Non-blocking 방식으로 원자성 보장.
4.자바의 동시성 이슈를 해결하는 방법
동시성 이슈에서는 가시성, 원자성 문제가 존재하는데 데이터의 변경 사항이 잘 못 적용되는 가시성 문제와 스레드의 적용 과정에서 간섭에의해 데이터 일관성이 깨지는 원자성 문제 두가지가 있습니다.
- 가시성 문제
- 한 스레드의 변경 사항이 다른 스레드에서 보이지 않는 문제
- volatile 키워드
- synchronized 키워드
- Lock 인터페이스
- ReentrantLock 등으로 동기화를 명시적으로 제어
- 원자성 문제 (Atomicity Issue)
- 여러 단계의 작업이 중간에 간섭을 받아 데이터 일관성이 깨지는 문제
- Atomic 클래스
- 내부적으로 CAS (Compare-And-Swap) 알고리즘을 사용하여 락 없이도 동기화 보장
- synchronized 키워드
- Lock 인터페이스
- ReentrantLock 등으로 동기화를 명시적으로 제어
이외에도 고려해야하는 동시성 문제
-
락 사용시 발생하는 문제들
-
데드락 방지
- 동기화를 잘못 사용하면 데드락(Deadlock)이 발생할 수 있습니다.
- 여러 락을 사용하는 경우, 항상 동일한 순서로 락을 획득하도록 설계해야 합니다.
-
락 경합 줄이기
- 스레드 간 락 경쟁이 심하면 성능 저하가 발생합니다.
- 락 대신 Atomic 클래스나 병렬 처리 컬렉션(ConcurrentHashMap, CopyOnWriteArrayList) 사용을 고려해야합니다.
5.volatile 키워드가 무엇인가요?
가시성 문제를 해결하는 자바에서 변수앞에 붙이는 키워드입니다.
volatile 키워드는 변수의 데이터를 스레드의 로컬 캐시가 아닌 메인 메모리(RAM)와 동기화됩니다.
한 스레드가 volatile 변수를 업데이트하면, 다른 스레드가 항상 최신 값을 읽을 수 있습니다.
volatile은 명령어 재배치도 방지합니다.
- JVM은 최적화를 위해 여전히 로컬캐시를 활용할 수 있습니다.
- 그러나 읽기 전에는 로컬 캐시를 무효화하고, 쓰기 후에는 메모리를 동기화 하기 때문에, 최종적으로 모든 스레드는 동일한 최신 값을 보게 됩니다.
- 단 단순한 읽기,쓰기 작업에는 문제가 없지만, 복합 연산(x++)에서는 데이터 일관성이 깨질 수 있습니다.
11.가시성 문제에 대해 조금 더 자세히 설명해 주세요. 여러 스레드가 모두 한 CPU의 캐시 메모리를 읽으면 가시성 문제가 발생하지 않을 것 같은데, 어떻게 생각하시나요?
- 혹시 동일한 CPU 코어면 캐시레벨도 동일 한건가요 ?
- 캐시레벨도 동일하다면 CPU 캐시와 메인 메모리 간의 동기화 지연이 문제가 되지 않을 것 같습니다.
동일한 CPU코어의 공유캐시를 사용하는 경우 가시성 문제가 발생하지 않을 수 있습니다
하지만 다른 캐시레벨의 저장소를 참조한다면 멀티 스레드 환경에서는 여전히 가시성 문제가 발생할것 같습니다.
- 각각의 스레드가 다른 캐시레벨(L1, L2, L3)를 가르켜도 가시성 문제가 발생합니다.
- 캐시 일관성 : 현대 CPU는 캐시 일관성 프로토콜(Cache Coherence Protocol)을 사용해 캐시 간 데이터 일관성을 보장합니다.
- 대표적인 프로토콜: MESI (Modified, Exclusive, Shared, Invalid).
6.synchronized 키워드가 무엇인가요?
멀티스레드 환경에서 동기화를 보장하기 위해 메서드나 블록 앞에 사용되는 키워드입니다. synchronized키워드는 모니터 락을 이용하여 스레드를 제어합니다
이를 통해 공유 자원에 여러 스레드가 동시에 접근할 때 발생할 수 있는 데이터 무결성 및 동시성 문제를 방지합니다
-
락 획득 및 해제
- synchronized를 사용하면 JVM은 모니터 락(Monitor Lock)을 사용하여 스레드 간 동기화를 보장합니다.
- 모니터 : 모니터 락을 관리하여 누가 모니터 락을 획득할지 정해줍니다.
- 모니터락 : 실행 할 수 있는 권리입니다, 하나만 획득 가능합니다.
동작 과정
- 스레드가 동기화된 코드에 진입하려고 할 때, 해당 객체 또는 클래스의 모니터 락을 요청.
- 모니터 락이 이미 다른 스레드에 의해 점유 중이면, 요청한 스레드는 대기 상태(Waiting)로 전환.
- 락이 해제되면, 대기 중인 스레드 중 하나가 락을 획득하고 동기화된 코드 실행
- 스레드가 동기화된 코드에 접근하려면 해당 객체 또는 클래스의 락을 획득해야 합니다.
- 락이 점유 중이면 다른 스레드는 대기 상태에 들어갑니다.
- 락 점유 순서
- 대기열의 첫번째 스레드가 아닌 대기중인 스레드 중에서 랜덤하게 선택될 수 있습니다.
- 운영체제의 스레드 스케줄러는 스레드의 우선순위를 고려하여 락을 부여할 수 있습니다.
- 일부 JVM은 큐(First In First Out)로 관리, 먼저 대기한 스레드에게 락을 부여합니다.
-
바이트코드 수준
- synchronized 키워드는 컴파일 시 JVM 바이트코드의 monitorenter 및 monitorexit 명령어로 변환됩니다.
- monitorenter: 락 획득.
- monitorexit: 락 해제.
- 바이트코드는 실행 전에 JIT(Just-In-Time) 컴파일러에 의해 기계어로 변환되며, 이 과정에서 추가적인 최적화가 수행됩니다.
- 이때 JIT 컴파일러는 불필요한 락을 제거하거나 성능을 최적화합니다.
- 바이트코드 수준의 명령어를 실행함으로 성능 최적화와 동기화 안정성을 제공합니다
7.synchronized의 문제점은 무엇이 있나요?
synchronized는 락을 사용하여 동기화를 지원합니다.
락을 사용하면 스레드가 락을 획득할 때까지 대기해야 하므로 성능 저하가 발생할 수 있습니다. 또한, 잘못된 락 관리로 인해 데드락(교착 상태)이 발생할 위험도 존재합니다. 그리고 과도한 락 사용은 시스템의 확장성을 제한하여 병렬성을 확보하려는 다른 작업에서도 성능 저하를 초래할 수 있습니다.
- 블로킹 메커니즘
- 스레드가 락을 획득할때까지 대기해야하므로 자원 낭비 및 성능 저하 발생
- 데드락
- 두 개 이상의 스레드가 서로의 락을 기다리는 상태로 인해 발생합니다.
- 교착 상태 방지하기
- 락 획득하는 순서를 지정하기
- 타임아웃 설정 : ReentrantLock을 사용하여 락을 대기할 최대 시간을 설정합니다
- 과도한 락 사용으로 병렬성을 확보해야하는 상황에서도 성능저하를 불러 일으킵니다.
13.Vector, Hashtable, Collections.SynchronizedXXX의 문제점은 무엇인가요?
모든 메서드에 대한 동기화 처리로 불필요한 락 경합이 발생하고 읽기 작업과 쓰기 작업 모두 동일한 락을 사용하므로, 병렬 처리 성능이 저하됩니다
14.SynchronizedList와 CopyOnArrayList의 차이를 설명해 주세요.
둘다 동기화를 보장하는 리스트 객체라는 점은 동일합니다
SynchronizedList는 간단한 동기화가 필요한 환경에서 적합하지만 병렬 처리 성능은 낮습니다.
CopyOnWriteArrayList는 락을 사용하지않고 읽기 작업이 많은 환경에서 성능이 우수하고 쓰기 작업이 적을 때 적합합니다.
SynchronizedList가 동기화 안정성이 더 높습니다. (바이트코드에서 락이 걸려있기 때문에 다른 간섭을 막기때문에 동기화가 더 안정적입니다.)
SynchronizedList
- 동작 방식
- 동기화
- 내부적으로 synchronized 키워드를 사용하여 단일락으로 리스트 메서드를 동기화합니다.
- 락 기반
- 단일 락을 사용하여 한 번에 하나의 스레드만 리스트에 접근할 수 있습니다.
- 읽기와 쓰기 모두 동기화
- 모든 작업에 락을 사용하므로 읽기 쓰기 모두 혼합된 환경에서 안전합니다.
- 문제점
- 모든 작업에 모두 락을 사용하므로, 성능이 떨어질 수 있습니다.
- 한번에 하나의 스레드만 접근 가능합니다.
CopyOnWriteArrayList
-
동작 방식
- 쓰기 작업 시 리스트를 복사하여 새로운 배열로 변경
- 읽기 작업은 기존 배열(스냅샷)을 참조하여 락 없이 안전합니다.
- 락없이 수행되므로 읽기 성능이 우수합니다
- 쓰기 작업 중에도 읽기 작업은 기존 데이터에 영향을 받지 않음.
-
문제점
- 쓰기 비용 증가
- 쓰기 작업마다 리스트 복사가 이루어지므로, 데이터 크기가 크거나 쓰기 작업이 많으면 성능 저하됩니다.
- 쓰기 작업이 많으면 메모리 사용량이 크게 늘어날 수 있음.
8.synchronized는 어떻게 구현되어 있나요?
synchronized는 자바에서 동기화를 제공하기 위한 키워드로, 스레드 간의 동기화를 보장하여 공유 리소스의 데이터 일관성을 유지합니다.
synchronized는 JVM 수준에서 바이트코드로 관리되며, 객체의 모니터 락(Monitor Lock)을 사용하여 구현됩니다.
- 모니터 (Monitor)
- 모니터 락을 관리하며 모니터락 획득을 정합니다.
- 모니터 락(Monitor Lock)
- 모든 객체(Object)는 하나의 모니터(Monitor)를 가지고 있으며, 이는 JVM이 관리합니다.
- synchronized 블록이나 메서드를 사용할 때, 스레드는 해당 객체의 모니터 락을 획득해야만 블록/메서드에 접근 가능합니다.
- 락을 획득한 스레드만 블록/메서드에 접근할 수 있으며, 다른 스레드는 대기 상태에 들어갑니다.
- 락 획득 및 해제
- 스레드가 synchronized 블록에 진입하면 락을 획득하고, 블록을 빠져나오면 자동으로 락을 해제합니다.
- JVM이 락 획득 및 해제를 자동으로 처리하므로, 개발자가 직접 관리할 필요가 없습니다.
synchronized는 JVM 수준에서 관리되므로 사용이 간편하지만, 성능과 병렬 처리 효율성이 요구되는 환경에서는 Lock 인터페이스와 같은 대안이 더 적합할 수 있습니다.
15. ConcurrentHashMap의 동작 과정을 SynchronizedMap과 비교하여 설명해 주세요.
멀티스레드 환경에서 사용하기 위한 스레드 안전(Thread-Safe) 맵(Map) 컬렉션입니다.
-
SynchronizedMap
- SynchronizedMap은 Collections.synchronizedMap(Map) 메서드를 통해 기존의 Map을 동기화된 맵으로 변환합니다.
- 모든 메서드(get, put, remove 등)에 synchronized 키워드를 추가하여 동기화를 보장합니다.
- 단일 락(Single Lock) 기반으로, 맵 전체에 대해 락을 사용.
-
ConcurrentHashMap
- ConcurrentHashMap은 부분 동기화(Partial Synchronization) 또는 버킷 기반 동기화(Bucket-Level Synchronization) 방식을 사용합니다.
- 데이터를 저장하는 Node 배열을 사용하며, 각 버킷(Node)은 연결 리스트 또는 트리로 관리합니다.
- 자바 8 이후, 버킷 내 데이터가 많아지면 연결 리스트가 트리(Tree)로 변환되어 성능 최적화되어있습니다.
- 락 없는 읽기(Lock-Free Read)와 부분 동기화된 쓰기를 통해 동시성을 지원합니다.
- CopyOnWriteArrayList와 동일하게 get 연산은 락 없이 수행되며 최신 데이터를 반환합니다.
- 또한 put remove와 같은 쓰기 작업은 특정 버킷에만 락을 걸어 병렬 처리가 가능합니다.
- 버킷별로 락을 걸기 때문에 병렬처리가 수월합니다.
- A가 1번 버킷에 접근한다면 2,3,4번 등의 버킷은 다른 스레드가 접근 가능합니다.
9.atomic하다는 것이 무슨 의미인가요?
Atomic(원자적)이라는 것은, 더 이상 나눌 수 없는 최소 단위를 얘기합니다.
즉, atomic하다는 것은 작업이 중간에 끊기거나 다른 스레드가 간섭하지 않고, 시작부터 끝까지 완전하게 수행되는 것을 보장합니다.
-
분할 불가 (Indivisibility)
- 작업이 여러 단계로 이루어져 있더라도, 외부에서는 작업이 단일 단계로 수행된 것처럼 보입니다.
- 다른 스레드는 작업이 진행 중인 상태를 볼 수 없습니다.
-
스레드 안전성 (Thread-Safety)
- 멀티스레드 환경에서도 락(lock) 없이 안전하게 동작할 수 있도록 설계합니다
- CAS 알고리즘을 사용하여 작업을 원자적으로 수행합니다.
- 현재 값이 기대한 값과 같으면 값을 업데이트 (기대한값 = Atomic클래스의 내부함수의 결과값)
- 그렇제 않으면 실패하고, 작업을 재시도.
-
데이터 일관성 유지
- 작업이 중간에 중단되거나, 다른 스레드가 간섭할 수 없으므로 데이터가 항상 일관된 상태를 유지합니다.
12.CAS(Compare-And-Swap) 알고리즘에 대해 설명해 주세요.
CAS란 Non-blocking 메커니즘을 활용한 알고리즘입니다.
락 없이도 스레드 안전을 보장하며, 주로 AtomicInteger, AtomicLong과 같은 원자적 클래스에서 사용됩니다.
-
동작원리
- 현재 메모리에 저장된 값을 읽습니다.
- 메모리 값이 기대 값(expected value)과 일치하면, 새 값으로 교체(swap)합니다.
- 기대값과 메모리 값이 다르면, 아무 작업도 하지 않고 반복합니다.
-
주요 메서드
- get(): 현재 값 읽기.
- set(newValue): 값 설정.
- compareAndSet(expect, update): 기대값과 현재 값을 비교하고, 같으면 새로운 값으로 교체.
10.atomic 타입이 무엇인가요?
Atomic 타입은 멀티스레드 환경에서 락 없이도 원자적(Atomic) 연산을 제공하기 위해 설계되었습니다.
단순 연산에는 적합하지만, 복잡한 동기화가 필요한 경우 synchronized나 다른 동기화 기법을 고려해야 합니다.
- AtomicInteger : 정수(int) 타입의 원자적 연산 지원
- AtomicLong : 긴 정수(long) 타입의 원자적 연산 지원
- AtomicBoolean : 불리언(boolean) 타입의 원자적 연산 지원
- AtomicReference : 객체 참조 타입의 원자적 연산 지원
- AtomicStampedReference : 참조와 버전 번호(스탬프)를 함께 관리하여 ABA 문제 해결
동시성 프로그래밍에서의 큰 문제인 두가지
원자성을 해결하려면:
synchronized 또는 Atomic 클래스를 사용.
가시성을 해결하려면:
volatile 또는 synchronized를 사용.
둘 다 해결이 필요한 경우:
synchronized가 가장 간단한 선택지이며, Atomic 클래스를 사용할 때는 가시성도 따로 고려해야 합니다.
다만 synchronized 키워드는 간단하지만 JVM의 모니터락을 이용하므로 성능저하를 야기할 수 있습니다 따라서 JVM의 성능을 고려해야합니다