JAVA

동시성 문제와 동기화(synchronized)

silver-w 2024. 11. 24. 15:51

동시성 문제


두개 이상의 스레드가 공유자원을 여러 단계로 나누어 이용할 때 동시성 문제가 일어난다.
  '잔액'이라는 공유자원을 사용하고 로직을 (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) 경합 조건과 데이터 일관성 문제를 해결한다.
  - 경합 조건 : 두 개 이상의 스레드가 경쟁적으로 동일한 자원을 수정할 때 발생하는 문제
  - 여러 스레드가 동시에 읽고 쓰는 데이터의 일관성 유지

 

 


출처 : https://inf.run/NC7kS

 

김영한의 실전 자바 - 고급 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