Java adv1 - 동시성 문제

dev1·2024년 11월 28일

인터페이스 하나를 구현.

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로 메모리가시성을 해결해줬음에도, 위와같은 이상한 결과가 나오게됨..

왜? ===> 이 문제는 메모리가시성보다는, 동시성에 관한 문제 ...

그럼 어떻게해결?

0개의 댓글