동시성 문제에 대해서 다루지만, DB의 Locking 전략등을 다루지 않고, 순수한 Java 코드로 동시성 문제를 해결하는 내용입니다.
여러
스레드
혹은프로세스
가 동시에 같은 자원에 접근하거나 수정할려고 할 때, 예상치 못한 결과를 발생하는 문제를 말합니다.
은행 출금 예제
일 것이다.동기화
에 대해서 알아 보도록 한다!package bank;
public interface Account {
boolean deposit(int amount);
boolean withdraw(int amount);
int getMoney();
}
package bank;
public class AccountImpl implements Account{
private int money;
public AccountImpl(int initMoney) {
this.money = initMoney;
}
/**
* 입금
* @param amount
*/
@Override
public boolean deposit(int amount) {
this.money = money + amount;
return true;
}
/**
* 출금
* @param amount
* @return
*/
@Override
public boolean withdraw(int amount) {
System.out.println(Thread.currentThread().getName() + " : " + "withdraw() start");
//검증 로직
System.out.println(Thread.currentThread().getName() + " : " + "[검증] 검증 로직 실행");
if(amount > this.money){
System.out.println(Thread.currentThread().getName() + " : " + "[검증실패] 출금액 > 보유금액" + "현재 계좌:" + amount);
return false;
}
System.out.println(Thread.currentThread().getName() + " : " + "[검증성공] 검증 성공!");
//출금 로직
System.out.println(Thread.currentThread().getName() + " : " + "[출금] 출금 실행");
this.money = money - amount;
System.out.println(Thread.currentThread().getName() + " : " + "[출금] 출금 완료, 현재 계좌:" + money);
return true;
}
/**
* 현재 가진 돈 return
* @return
*/
@Override
public int getMoney() {
return this.money;
}
}
package bank;
public class main {
public static void main(String[] args) {
Account account = new AccountImpl(10000);
WithdrawThread withdrawThread1 = new WithdrawThread(account);
WithdrawThread withdrawThread2 = new WithdrawThread(account);
Thread t1 = new Thread(withdrawThread1);
Thread t2 = new Thread(withdrawThread2);
t1.start();
t2.start();
}
private static class WithdrawThread implements Runnable {
private final Account account;
public WithdrawThread(Account account) {
this.account = account;
}
@Override
public void run() {
//8000원 출금 진행.
account.withdraw(8000);
}
}
}
두 출금 로직 모두 완료가 되고, 현재 계좌는 마이너스 계좌가 되어버렸다...
왜 이런 결과가 나온 것일까?
출금이 가능
한 상태로 검증 로직을 통과한 것이다.volatile
을 사용 하여 회피한다.package bank;
public class AccountImplV2 implements Account{
private int money;
public AccountImplV2(int initMoney) {
this.money = initMoney;
}
/**
* 입금
* @param amount
*/
@Override
public synchronized boolean deposit(int amount) {
this.money = money + amount;
return true;
}
/**
* 출금
* @param amount
* @return
*/
@Override
public synchronized boolean withdraw(int amount) {
System.out.println(Thread.currentThread().getName() + " : " + "withdraw() start");
//검증 로직
System.out.println(Thread.currentThread().getName() + " : " + "[검증] 검증 로직 실행");
if(amount > this.money){
System.out.println(Thread.currentThread().getName() + " : " + "[검증실패] 출금액 > 보유금액" + "현재 계좌:" + amount);
return false;
}
System.out.println(Thread.currentThread().getName() + " : " + "[검증성공] 검증 성공!");
//출금 로직
System.out.println(Thread.currentThread().getName() + " : " + "[출금] 출금 실행");
this.money = money - amount;
System.out.println(Thread.currentThread().getName() + " : " + "[출금] 출금 완료, 현재 계좌:" + money);
return true;
}
/**
* 현재 가진 돈 return
* @return
*/
@Override
public synchronized int getMoney() {
return this.money;
}
}
synchronized
는 어떻게 동작하는 것일까?synchronized
키워드가 있는 메서드는, 이 모니터 락이 있어야만, 메서드에 진입이 가능하고, 없을 경우에는 접근이 불가능하며, state
를 block
상태로 변경한다.
Thread-0 먼저 접근한다고 가정을 진행하면, 다음과 같은 순서로 이뤄진다.
synchronized
를 통해 동기화를 시켜줘야 하는 점이다.synchronized
키워드를 도배했다고 생각하자.1. 임계 영역
2. synchronized (코드 블럭)
3. Lock (ReentrantLock)