인터페이스 하나를 구현.
package thread.sync;
public interface BankAccount {
boolean withdraw(int amount);
int getBalance();
}
이 인터페이스를 구현해야하는 클래스를 하나 만들자.
package thread.sync;
import static util.MyLogger.log;
import static util.ThreadUtils.sleep;
public class BankAccountV1 implements BankAccount {
private int balance;
public BankAccountV1(int initialBalance) {
this.balance = initialBalance;
}
@Override
public boolean withdraw(int amount) {
log("거래 시작 : " + getClass().getSimpleName());
log("[검증을 시작합니다] 출금액 : " + amount + " 보유중인 금액 : " + balance);
if ( balance < amount ) {
log("[검증 실패] 출금액 : " + amount + " 보유중인 금액 : " + balance + ", 잔액 부족입니다.");
return false;
}
// 출금가능상태
log("[검증 완료] 출금액 : " + amount + " 보유중인 금액 : " + balance);
log("[처리중]");
sleep(1000);
balance -= amount;
log("[검증 완료] 출금액 : " + amount + " 출금 후 잔액 : " + balance);
log("거래를 종료합니다.");
return true;
}
@Override
public int getBalance() {
return balance;
}
}
==> 출금시스템 / 잔액확인하는 코드
출금했을때 로직 ... -> 현재잔액이 출금금액 이상이여야만 출금해주게되는 ...
=======
Runnable 을 통해서 스레드를 생성
package thread.sync;
public class WithdrawTask implements Runnable{
private BankAccount account;
private int amount;
public WithdrawTask(BankAccount account, int amount) {
this.account = account;
this.amount = amount;
}
@Override
public void run() {
account.withdraw(amount);
}
}
==> 이 스레드를 시작하게되면, 생성자를 통해서 입력받은 계좌 출금시도 ㄱㄱ
이제, 활용하기위해 메인페이지 ㄱㄱ
package thread.sync;
import static util.MyLogger.log;
import static util.ThreadUtils.sleep;
public class BankMain {
public static void main(String[] args) throws InterruptedException {
BankAccount account = new BankAccountV1(1000);
Thread t1 = new Thread(new WithdrawTask(account, 800), "t1");
Thread t2 = new Thread(new WithdrawTask(account, 800), "t2");
t1.start();
t2.start();
sleep(500);
log("t1 state : " + t1.getState());
log("t2 state : " + t2.getState());
t1.join();
t2.join();
log("최종 잔액 : " + account.getBalance());
}
}
계좌잔액이 1000원인 계좌를 하나 만들었음 ( account )
스레드생성 ... => 같은계좌를 넣어주고, 그 계좌에서 800원 출금하는 기능 실행
t1.start() // t2.start() 를 통해서 800원 출금 [ 같은계좌 : account( 같은참조값 ) ]
14:11:54.344 [ t1] 거래 시작 : BankAccountV1
14:11:54.344 [ t2] 거래 시작 : BankAccountV1
14:11:54.348 [ t1] [검증을 시작합니다] 출금액 : 800 보유중인 금액 : 1000
14:11:54.348 [ t2] [검증을 시작합니다] 출금액 : 800 보유중인 금액 : 1000
14:11:54.348 [ t2] [검증 완료] 출금액 : 800 보유중인 금액 : 1000
14:11:54.348 [ t1] [검증 완료] 출금액 : 800 보유중인 금액 : 1000
14:11:54.348 [ t2] [처리중]
14:11:54.348 [ t1] [처리중]
14:11:54.835 [ main] t1 state : TIMED_WAITING
14:11:54.835 [ main] t2 state : TIMED_WAITING
14:11:55.354 [ t2] [검증 완료] 출금액 : 800 출금 후 잔액 : 200
14:11:55.354 [ t1] [검증 완료] 출금액 : 800 출금 후 잔액 : -600
14:11:55.355 [ t2] 거래를 종료합니다.
14:11:55.356 [ t1] 거래를 종료합니다.
14:11:55.361 [ main] 최종 잔액 : -600
==> 이상하게 로직이 돌아감 ... ( 검증하는부분이 이상하게 처리 ).
즉, t2 에서 검증완료하고 출금한다음에는, 계좌금액이 200원이니까 ...
같은계좌에서 출금하던 t1 에서는 현재금액 ( 200 ) 원 보다 출금금액 ( 800 ) 이 더 많으니 출금을 해주면안됨
[[ 그렇게 로직을 짜기도 했고 ... ]]
근데? 로직짜놓은것과 상관없이 그냥 출금 ...
왜 이런문제가 발생 ?
캐시메모리? ==> volatile 을 사용해보자
package thread.sync;
public class WithdrawTask implements Runnable{
private BankAccount account;
volatile private int amount;
public WithdrawTask(BankAccount account, int amount) {
this.account = account;
this.amount = amount;
}
@Override
public void run() {
account.withdraw(amount);
}
}
잔액부분에서 문제가 있으니 ... => volatile 을 통해서 캐시메모리에 있는거로 접근 ㄴㄴ ==> 메인메모리로 바로접근
[[ 값이 변경됐을때의 딜레이 삭제 ? ]]
===> 이후에 다시실행 ㄱㄱ
14:14:29.864 [ t1] 거래 시작 : BankAccountV1
14:14:29.865 [ t2] 거래 시작 : BankAccountV1
14:14:29.872 [ t2] [검증을 시작합니다] 출금액 : 800 보유중인 금액 : 1000
14:14:29.872 [ t1] [검증을 시작합니다] 출금액 : 800 보유중인 금액 : 1000
14:14:29.872 [ t2] [검증 완료] 출금액 : 800 보유중인 금액 : 1000
14:14:29.872 [ t1] [검증 완료] 출금액 : 800 보유중인 금액 : 1000
14:14:29.873 [ t2] [처리중]
14:14:29.873 [ t1] [처리중]
14:14:30.352 [ main] t1 state : TIMED_WAITING
14:14:30.353 [ main] t2 state : TIMED_WAITING
14:14:30.879 [ t2] [검증 완료] 출금액 : 800 출금 후 잔액 : 200
14:14:30.879 [ t1] [검증 완료] 출금액 : 800 출금 후 잔액 : -600
14:14:30.880 [ t2] 거래를 종료합니다.
14:14:30.880 [ t1] 거래를 종료합니다.
14:14:30.884 [ main] 최종 잔액 : -600
volatile로 메모리가시성을 해결해줬음에도, 위와같은 이상한 결과가 나오게됨..
왜? ===> 이 문제는 메모리가시성보다는, 동시성에 관한 문제 ...
그럼 어떻게해결?