| 구분 | 동시성 | 병렬성 |
|---|---|---|
| 실행 환경 | 단일 CPU | 멀티코어 CPU |
| 동작 방식 | Context Switching | 작업을 실제로 나누어 처리 |
| 목적 | 시스템 효율성 증가 | 작업 처리 속도 향상 |
일관성(Consistency) : 시스템의 상태가 정의된 규칙이나 제약 조건을 항상 만족하는 것
무결성(Integrity) : 데이터가 손상되지 않고 올바르게 유지되는 것
다음 코드에서 다중 스레드가 동시에 increment()를 호출하면, 값의 손실이 발생할 수 있다.
class Counter {
private int count = 0;
public void increment() {
count++; // 읽기 -> 증가 -> 쓰기
}
public int getCount() {
return count;
}
}
count++는 내부적으로 보면 3단계 작업으로 나뉜다.
count++는 단순한 하나의 연산처럼 보이지만 실제로는 3단계 작업으로 나뉜다.
1. 읽기 : count 변수의 현재 값을 읽음.
2. 증가 : 읽어온 값에 1을 더함.
3. 쓰기 : 증가된 값을 다시 count 변수에 저장.
- 여러 스레드가 동시에 실행하면 스레드 간 작업이 섞여서 발생하는 경쟁 상태(Race Condition)로 인해 값이 예상과 다르게 나올 수 있다.
-> 이러한 문제를 해결하려면 스레드 안전성을 보장해야 한다.
synchronized는 임계 영역(Critical Section)을 한 번에 하나의 스레드만 실행하도록 락을 걸어준다.
가시성 문제와 원자성 문제를 동시에 해결할 수 있다.
public synchronized void increment() {
count++;
}
AtomicInteger와 같은 Atomic클래스들은 락을 사용하지 않고 스레드를 안전하게 연산한다. 내부적으로는 CAS(Compare-And-Swap) 알고리즘을 사용한다.
import java.util.concurrent.atomic.AtomicInteger;
class AtomicCounter {
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
CAS 알고리즘
👍 CAS(Compare-And-Swap) 알고리즘은 변수의 값을 비교하여 예상 값과 일치하면 새로운 값으로 교체한다.
👎 락을 사용하지 않아 성능이 뛰어나지만 실패 시 다시 재시도하므로 무한 루프에 빠질 가능성이 있다.
상태 변경이 불가능한 불변 객체를 사용하면 스레드 안전성을 보장할 수 있다.
모든 필드는 final로 선언되며, 초기화 후 변경할 수 없다. → 스레드 안전성을 보장한다.
동기화를 사용하지 않지만 변하지 않는 객체를 사용하는 것은 스레드 안전성을 보장하는 일.
final class ImmutableObject {
private final int value;
public ImmutableObject(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
CopyOnWriteArrayList
- 읽기 작업이 많고 쓰기 작업이 드문 경우에 주로 사용한다.
- 쓰기 시 기존 데이터를 복사하여 새로운 데이터로 교체한다.
ConcurrentHashMap
- 스레드 안전한 해시맵 구현체.
- 락 분할(Lock Stripping) 기술을 사용하여 여러 스레드가 동시에 접근할 수 있다.
가시성 문제(Visibility Issue)는 한 스레드에서 변경한 값이 다른 스레드에서 보이지 않는 문제를 의미한다.
스레드는 CPU 캐시에 데이터를 저장하고 작업을 수행한다. 메인 메모리와 캐시 간의 비동기적 데이터 동기화로 인해 한 스레드의 변경 사항이 다른 스레드에 반영되지 않을 수 있다.
모든 스레드가 같은 CPU의 캐시를 사용하면 가시성 문제가 발생하지 않을까?
아니다. 같은 CPU 캐시를 공유하면 스레드간 읽기/쓰기 데이터가 캐시에서 바로 전달된다. 그렇기 때문에 가시성 문제가 줄 수는 있다. 하지만 완벽하게 해결할 수 없는데 그 이유는 가시성 문제의 근본적인 원인이 JMM에 있기 때문이다. Java Memory Model은 스레드 간 데이터 읽기/쓰기 규칙을 정의한다. 이 때, 명령어 재정렬이 이뤄질 수 있는데 Java 컴파일러와 프로세서가 실행 속도를 높이기 위해 명령어의 순서를 재배치한다. 이로 인해 다른 스레드가 데이터에 접근할 때 예상치 못한 순서로 값을 읽거나 수정할 수 있다.
원자성 문제(Atomicity Issue)는 하나의 작업이 여러 단계로 나뉘어 중간에 다른 스레드가 개입할 때 발생한다.
위에서 예를 들었던 것 처럼 count++ 증감연산자는 원자적 연산이 아니라 내부적으로 필드에 여러번 접근하기 때문에 동시에 두 스레드가 increment()를 호출하면 값 손실이 발생할 수 있다.
문제점
동시성 해결방식과 스레드 안전성을 보장하는 방식은 위에 글에서 보는 것 처럼 매우 유사하다.
고로 스레드 안전성을 동시성 문제 해결 ⊃ 스레드 안전성 보장 라고 생각하면 될 듯 하다.
1. Vector
List 인터페이스를 구현하며, 모든 메서드가 동기화(synchronized)되어 스레드 안전성을 보장한다. 내부적으로 동기화 처리로 인해 단일 스레드 환경에서는 ArrayList보다 성능이 떨어진다.Collections.synchronizedList() 또는 CopyOnWriteArrayList를 사용하는 것이 권장된다.2. Hashtable
HashMap과 유사하지만 동기화를 기본적으로 지원하는 레거시 클래스.ConcurrentHashMap 사용으로 대체할 수 있다.3. Collections.synchronizedXXX
List, Set, Map)에 동기화 래퍼를 제공하여 스레드 안전성을 추가한 형태이다.Collections.synchronizedList(new ArrayList<>())4. CopyOnWriteArrayList
5. ConcurrentHashMap
HashMap의 동시성 버전으로, 부분 락(Segmented Locking)을 사용하여 성능을 최적화한다.이번 회차에도 역시나 열심히 준비해 온 스터디원들로 가득했다. 스터디 준비를 많이 못했어도 참여하는게 정신력 측면에서 도움이 된다고 생각한다. 취준기간이 길어지면 길어질수록 사람은 나태해지고 해이해지는데 같이 공부하는 팀원들이 열심히 하는 모습을 보면 정신을 차리게 된다.
일단 이번 스터디에서 내가 대답하지 못한 질문들은
synchronized 동작방식에서 자바의 모니터락이란?
한 번에 하나의 스레드만 동기화된 코드에 접근할 수 있도록 배타적 접근을 보장하는 방식으로 멀티 스레드 환경에서 동기화를 제어하는 역할을 한다.
ConcurrentHashMap 의 동작방식에 대하여
-> 코드를 직접 본적 있는지, 내부적으로 synchornized를 쓴다던가, 읽기 연산에는 synchonrized를 사용하지 않는다던가
ConcurrentHashMap은 멀티스레드 환경에서 동시성을 보장하는 Map으로 버킷 단위 락을 사용한다. 쓰기 연산에는 synchornized를 쓰는데 읽기 연산에서는 쓰지 않는 이유는 구조적으로ConcurrentHashMap은 읽기 연산에서 객체를 변경하지 않기 때문이다.synchornized를 사용했을 때 성능 문제랑 교착상태 관련된 잘못된 프로그래밍 관련
-> 운영체제 데드락 관점으로 꼬리질문
synchronized는 락을 사용해 스레드 간 자원 접근을 동기화하지만 성능 문제가 발생할 수 있다. 락을 사용하면 스레드 대기가 발생하고, 불필요하게 과도한 동기화는 경쟁 상태으로 병목현상을 초래한다. 그리고 두 개 이상의 스레드가 서로 락을 기다리는 상황이 발생하면 데드락에 빠질 수도 있기 때문에 조심해야 한다.
데드락을 예방하기 위해서는 여러 스레드가 동일한 순서로 락을 획득하도록 강제하는 것이 좋다. 모든 스레드가 락을 항상 동일한 순서로 획득하도록 설계하면 순환 대기가 발생하지 않아 데드락을 방지할 수 있다.
저번 회차 멀티 스레드에 이어서 동시성 프로그래밍에 대해서 공부를 해보았다. 내가 이전에 코딩하면서 이런것들을 고려해본적 있는가? NO... .. . . . 진짜 공부하면 할 수록 이걸 진짜 나만 모른다고???? 이 친구들은 이걸 다 알고 해본적도 있다고? 심지어 이런걸 고려해서 아키를 구성했다고? 의 의문의 연속 + 현타의 연속
공부를 많이 해가야 양질의 질문도 할 수 있기에 남은 마지막 차수도 열심히 준비해가야겠다. 공부하면서 이건 왜 그럴까? + 이걸 안쓴다면 어떻게 해결할 수 있을까 고민했던 질문들을 미리 정리해두면 면접관 타임에 질문하기 편하다.
P.S. 면접이 끝날 때마다 구글 폼으로 면접자에 대한 피드백을 써주는데 이게 복기하기에 정말 좋다 !