동시성 문제
두개 이상의 스레드가 공유자원을 여러 단계로 나누어 이용할 때 동시성 문제가 일어난다.
'잔액'이라는 공유자원을 사용하고 로직을 (1)잔액 확인 (2)잔액 출금 으로 나누어져있을 때
[한 번에 여러개의 스레드 실행할 경우]
한 스레드가 공유자원인 잔액의 값을 (2)출금 로직에서 변경했을 때
다른 스레드에서는 '잔액'을 처음으로 설정한 값을 기준으로(변경되지 않은 값을 기준으로) 검증을 하면 원치 않는 결과 초래
15:38:50.830 [ t2] 거래 시작 : BankAccountV1
15:38:50.830 [ t1] 거래 시작 : BankAccountV1
15:38:50.840 [ t1] [검증 시작] 출금액 : 800, 잔액 : 1000
15:38:50.840 [ t2] [검증 시작] 출금액 : 800, 잔액 : 1000
15:38:50.840 [ t1] [검증 완료] 출금액 : 800, 잔액 : 1000
15:38:50.840 [ t2] [검증 완료] 출금액 : 800, 잔액 : 1000
15:38:51.317 [ main] t1 state : TIMED_WAITING
15:38:51.317 [ main] t2 state : TIMED_WAITING
15:38:51.851 [ t1] [출금 완료] 출금액 : 800, 잔액 : -600
15:38:51.851 [ t2] [출금 완료] 출금액 : 800, 잔액 : 200
15:38:51.853 [ t2] 거래 종료
15:38:51.853 [ t1] 거래 종료
15:38:51.860 [ main] 최종 잔액 : -600
[한 번에 하나의 스레드만 실행]
원자성 보증. '잔액'의 값은 스레드 실행 중간에 변하지 않아 위 문제가 해결된다.
- 임계 영역 : (2)잔액 출금 부분
- 여러 스레드가 동시에 접근 시 데이터 불일치 및 예상치 못하는 동작이 발생할 수 있는 위험하고 중요한 코드 부분
- 여러 스레드가 동시에 접근해선 안되는 공유 자원을 접근하거나 수정하는 부분을 의미한다.
- synchronized 키워드를 통해 임계 영역을 보호할 수 있다.
synchronized 메서드
임계 영역이 일어나는 메서드에 syncronized를 붙여주면, 해당 메서드는 한번에 하나의 스레드만 사용할 수 있게 된다.
다른 스레드는 앞 스레드가 synchronized 메서드를 완료할 때 까지 기다려야한다.
15:35:23.623 [ t1] 거래 시작 : BankAccountV2
15:35:23.635 [ t1] [검증 시작] 출금액 : 800, 잔액 : 1000
15:35:23.635 [ t1] [검증 완료] 출금액 : 800, 잔액 : 1000
15:35:24.110 [ main] t1 state : TIMED_WAITING
15:35:24.110 [ main] t2 state : BLOCKED
15:35:24.636 [ t1] [출금 완료] 출금액 : 800, 잔액 : 200
15:35:24.636 [ t1] 거래 종료
15:35:24.636 [ t2] 거래 시작 : BankAccountV2
15:35:24.637 [ t2] [검증 시작] 출금액 : 800, 잔액 : 200
15:35:24.637 [ t2] [검증 실패] 출금액 : 800, 잔액 : 200
15:35:24.639 [ main] 최종 잔액 : 200
※ 모든 인스턴스는 내부에 자신만의 락(lock)을 가지고 있다. (모니터 락, monitor lock)
t1 스레드는 해당 인스턴스의 락을 획득하고 있기때문에 synchronized withdraw()를 실행하고,
t2 스레드는 해당 인스턴스의 락이 없기 때문에 synchronized withdraw()를 실행할 수 없어 BLOCKED상태로 대기한다.
= 락을 획득하기 전까지 계속 대기하고 CPU 실행 스케줄링에 들어가지 않는다.
※ 참고로 스레드 간 인스턴스 락 획득 순서는 보장되지 않고, volatile을 사용하지 않아도 메모리 가시성 문제도 해결된다.
단, 한 번에 하나의 스레드만 실행할 수 있기 때문에, 속도가 느리다.
따라서 동시에 실행할 수 없는 코드 구간은 꼭 필요한 곳으로 한정해서 설정해야한다.
// (1) 블록 동기화 : 매개변수로 락 대상의 인스턴스를 넣는다
synchronized (this) {
<임계 영역 코드>
}
// (2) 메서드 동기화 : 메서드에 synchronized를 걸때는 매개값 생략 (기본값은 (this)이다)
public synchronized boolean task() {
<임계 영역 코드>
}
동기화
(1) 여러 스레드가 동시에 접근할 수 있는 자원(객체, 메서드)에 대해 일관성 있고, 안전한 접근을 보장 (2) 멀티스레드 환경에서 발생할 수 있는 문제, 데이터 손상이나 예기치 않은 결과를 방지하기 위해 사용한다. (2) 경합 조건과 데이터 일관성 문제를 해결한다. - 경합 조건 : 두 개 이상의 스레드가 경쟁적으로 동일한 자원을 수정할 때 발생하는 문제 - 여러 스레드가 동시에 읽고 쓰는 데이터의 일관성 유지 |
김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성 강의 | 김영한 - 인프런
김영한 | 멀티스레드와 동시성을 기초부터 실무 레벨까지 깊이있게 학습합니다., 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바 문법을 안다?
www.inflearn.com
'JAVA' 카테고리의 다른 글
Map.Entry와 Map.getKey 차이 (0) | 2024.12.29 |
---|---|
중첩 클래스 (0) | 2024.12.22 |
멀티스레드 메모리 접근 방식/메모리 가시성/happens-before (0) | 2024.11.21 |
스레드와 Yield (0) | 2024.11.18 |
스레드 인터럽트 (0) | 2024.10.31 |