쓰레드 동기화
1) 쓰레드 동기화
- 멀티 스레드의 문제점 (다수의 스레드가 공유 데이터에 동시 접근)의 해결책
-> 공유 데이터를 배타적으로!
(1) synchronized로 동기화 블록 지정
- 공유 데이터에 접근하는 모든 스레드의 한 줄 세우기
- 한 스레드가 독점 실행해야하는 부분(동기화 코드)을 표시하는 키워드
- 임계영역 지정 -> 한 번에 하나의 쓰레드만이 공유 데이터 접근
-> synchronized를 붙이면 특정 쓰레드가 해당 메서드를 실행하는 동안 객체에 락(Lock)을 걸어 단 하나의 쓰레드만 접근할 수 있도록 차단하므로 데이터의 일관성과 안전성이 보장됨!
(2) wait()-notify() 메소드로 스레드 실행 순서 제어
- 한 스레드가 공유 데이터에 대한 작업을 끝낼 때까지 다른 스레드가 대기

순서: 소비자가 먼저 기다림(wait()) -> 생산자가 데이터 저장 -> 생산자가 소비자에게 다 썼다고 알려줌(notify()) -> 생산자가 기다림(wait()) -> 소비자가 데이터를 읽음
2) 쓰레드 상태제어
(1) join()
- 해당 쓰레드가 완료될 때까지 다른 쓰레드가 기다리게 함
- join(), join(long miles)

순서: A가 B를 실행 -> B가 run() 실행 -> A가 threadB.join() 호출 -> A는 B가 끝날 때까지 대기(일시정지)
=> ThreadA: threadB.start() → 바로 이어서 threadB.join() 호출 → ThreadB가 끝날 때까지 대기.
ThreadB: run() 실행 → 끝날 때까지 계속 진행.
=> B 먼저 끝 -> A 이어서 실행으로 보장됨!
cf. 만약 join()을 쓰지않을 경우
ThreadA: threadB.start() → 바로 다음 코드로 넘어감 (대기 없음)
ThreadB: 동시에 run() 실행 시작
=> ThreadA와 ThreadB가 동시에 실행되어 실행순서가 예측 불가능해지고, 두 스레드가 병렬로 동작함.
(2) wait()
- WAITING: 시간 지정 없이 무한 대기 -> notify로 깨움
- TIMED_WAITING: 지정 시간동안 대기
- BLOCKED: 다른 쓰레드가 가진 lock 기다리는 중이라 실행x
.
.
.
.
.
쓰레드 동기화를 실전에 적용해보기 위해 ppt에 있는 FamilyAccount(lab06) 코드를 lab 07,08를 참고해서 수정해보았다.
#lab06-> lab06+07(join)+08(sleep, wait-notify)
- synchronized 붙이기 전


1000원씩 인출

1500원씩 저축

저축과 인출 쓰레드가 동시 진행됨 -> 동시에 계좌 잔액을 수정하면서 잔액 계산이 꼬이는 현상(Race condition) 발생

해결해야하는 부분:
- synchronized가 없음 -> 입금과 출금 중 잔액을 읽고 쓰는 연산이 동시에 진행 -> 잔액이 엉켜서(race condition) 엉뚱한 잔액이 나옴
- 입금, 저축이 완료되지 않은 상태에서 getbalance() 호출로 최종 잔액이 출력될 수 있음. -> 입출금이 진행중일때 이미 끝난 작업이 진행 중인 작업을 기다려야 함
- 저축 다음에 인출이 일어나야함 (순서 제어 필요!)
Q. 그렇다면 synchronized만으로 인출, 저축 순서를 제어하면서, 잔액이라는 데이터 수정/저장하는 과정이 꼬이지 않게 할 수 있을까?
A. synchronized로만은 부족하다. synchronized의 역할은 입금 작업이 완전히 끝날 때까지 출금 작업이 잔액 데이터에 숟가락을 얹지 못하게 막는것이다!
무조건 입금이 먼저 실행되고 그 다음에 출금이 실행된다는 순서를 보장해주지 않는다.
=> wait()-notify()를 통해 입금->출금이라는 순서를 제어할 수 있다!
- synchronized과 join() 추가 후


synchronized와 join으로 충분히 전에 있었던 해결책 3개를 모두 해결할 수 있을 줄 알았는데, 아직 3번(순서 제어) 문제가 해결되지 않았다. 이 문제는 wait()-notify()를 통해 입금 -> 출금이라는 순서를 따로 지정해줘야할 것 같다.
Q. 만약에 join()이 없다면?
join()의 역할은 "입금 쓰레드와 출금 쓰레드가 전부 끝날 때까지 메인 쓰레드가 기다렸다가, 마지막에 최종 잔액을 출력해라"임
-> join()이 없다면 입금이 출금보다 먼저 끝났을 경우(for문이 다 돌았을 경우) 잔액이 출금과정까지 반영하지 않은 상태로 미완성 잔액이 출력될 수 있다.
cf. join을 예외처리하기 위해 main에 throws InterruptedException이 붙음.

- 처음에는 위와 같이 인출과정과 저축과정이 번갈아 출력되지 않아 보여서 Thread.sleep에서의 대기시간이 달라서 문제가 되는줄 알았다.
그치만 알고보니 wait()과 notify()를 사용하면 슬립 시간이 다르거나 아예 없더라도 프로그램의 로직(신호 교환)에 의해 순서가 완벽하게 제어되는 것이었다..

- [저축1] $$예금~~ \n (인출1) !!인출 실패! ~~~ 이렇게 저축과 인출결과가 한 뭉텅이로 나오게 하고 싶은데 저축은 run() 메소드에 있고 예금/인출 금액 결과는 BankAccount의 withdraw/deposit 메소드에 있음 -> CPU가 두 쓰레드를 번갈아 실행
해결: 두 개의 출력결과를 하나의 메소드에 몰아주기(BankAccount로!) + 조금 더 클린하게 만들기
=> 해결 후 최종!!






해결하지 못한 문졔: 최종 출력결과(getbalance)가 출력되지 않는다. <- 인출자 쓰레드에서 잔액이 부족해서 wait()을 하며 영원히 저축을 기다리고 있기 때문..
그래서 wi.join()이 안 끝나서 그 밑에 거래종료가 안 되는 것!!
=> 해결책: 예금과 출금 금액을 같게 한다(굳이..?), 예금 횟수를 인출 횟수보다 늘린다.(이렇게 하면 wi가 먼저 끝나고 de가 그 다음 끝나서 최종 결과도 뜰 수 있을듯!)