멀티스레드 문제 예시 - 은행 계좌 잔액 갱신 문제
class BankAccount {
private int balance = 100;
public void withdraw(int amount) {
if (balance >= amount) { // 조건 검사
balance -= amount; // 잔액 갱신
}
}
}
의도된 동작
- 스레드 A가 50을 출금.
- 스레드 B가 50을 출금.
- 결과적으로 잔액은
0
이 되어야 함.
문제 상황
- 스레드 A와 스레드 B가 동시에
withdraw(50)
메서드를 호출하면, 잔액 조건 검사와 갱신 작업이 중첩될 수 있음. - 이로 인해 두 스레드가 잔액 조건을 동시에 검사한 후, 동시에 출금을 진행.
- 결과적으로, 잔액이
50
으로 잘못 계산됨.
**정상적인 단일 스레드 실행**
-------
스레드 A 실행:
[검사] 잔액: 100 >= 50 -> True
[갱신] 잔액 -= 50 -> 잔액: 50
**멀티스레드에서의 충돌**
스레드 A 실행: 스레드 B 실행:
[검사] 잔액: 100 >= 50 -> True [검사] 잔액: 100 >= 50 -> True
[갱신] 잔액 -= 50 -> 잔액: 50 [갱신] 잔액 -= 50 -> 잔액: 50
결과: 두 스레드가 모두 출금을 완료했지만, 최종 잔액은 50
으로 계산됨 (잘못된 결과).
멀티스레드 환경의 위험 요소
- Race Condition (경쟁 상태)
- 여러 스레드가 동시에 동일한 자원에 접근하고 이를 수정할 때, 실행 순서에 따라 결과가 달라질 수 있음.
- 예: 위의 은행 계좌 예제에서 스레드 간 동기화가 없을 경우 발생.
- Deadlock (교착 상태)
- 두 스레드가 서로 자원을 기다리며 무한히 대기하는 상황.
- 예: 스레드 A가 자원 1을 점유하고 자원 2를 기다리는 동안, 스레드 B는 자원 2를 점유하고 자원 1을 기다림.
- Data Corruption (데이터 손상)
- 여러 스레드가 동일한 메모리 위치를 수정하면, 결과값이 예기치 않게 손상될 수 있음.
- 예: 공유 데이터 구조의 상태가 불완전하거나 잘못된 상태로 유지됨.
- Thread Interleaving (스레드 중첩 실행)
- 스레드가 실행 중에 문맥 교환(Context Switching)으로 인해 실행 흐름이 중첩됨.
- 예: 스레드 A와 B가 교차 실행되며 연산이 중간 상태에서 중단됨.
해결 방법
1. 동기화 사용
- synchronized 키워드: 공유 자원에 한 번에 하나의 스레드만 접근하도록 보장.
- ReentrantLock: 더 세밀한 락 제어 가능.
public synchronized void withdraw(int amount) {
if (balance >= amount) {
balance -= amount;
}
}
2. 원자적 연산 사용
- Java의
AtomicInteger
또는AtomicLong
과 같은 클래스 사용. - 내부적으로 CAS(Compare-And-Swap)를 활용하여 동기화 문제를 해결.
import java.util.concurrent.atomic.AtomicInteger;
class BankAccount {
private AtomicInteger balance = new AtomicInteger(100);
public void withdraw(int amount) {
balance.addAndGet(-amount); // 원자적 연산
}
}
3. 동시성 제어 도구
CountDownLatch
,Semaphore
,CyclicBarrier
등 사용.- 특정 조건에서 스레드의 실행을 제어하여 동기화 문제를 방지.
'호기심 천국 > Java' 카테고리의 다른 글
AtomicInteger (멀티스레드 안전) 겉 핥기 (0) | 2025.01.19 |
---|---|
StringBuilder vs String 문자열합치기 (0) | 2023.09.19 |