728x90
싱글 스레드 프로세스 : 데이터에 단 하나의 스레드만 접근하기 때문에 상관없음
멀티 스레드 프로세스 : 두 스레드가 동일한 데이터를 공유하게 되어 문제 발생 가능성 존재함
하나의 계좌에서 현금을 출금한다고 가정한다
더보기
package 스레드;
public class Main {
public static void main(String[] args) {
Runnable threadTask = new ThreadTask();
Thread thread1 = new Thread(threadTask);
Thread thread2 = new Thread(threadTask);
thread1.setName("김이룸");
thread2.setName("이자바");
thread1.start();
thread2.start();
}
}
class Account {
// 잔액을 나타내는 변수
private int balance = 1000;
public int getBalance() {
return balance;
}
// 인출 성공 시 true, 실패 시 false 반환
public boolean withdraw(int money) {
// 인출 가능 여부 판단 : 잔액이 인출하고자 하는 금액보다 같거나 많아야 합니다.
if (balance >= money) {
// if문의 실행부에 진입하자마자 해당 스레드를 일시 정지 시키고,
// 다른 스레드에게 제어권을 강제로 넘깁니다.
// 일부러 문제 상황을 발생시키기 위해 추가한 코드입니다.
try {
Thread.sleep(1000);
} catch (Exception error) {
}
// 잔액에서 인출금을 깎아 새로운 잔액을 기록합니다.
balance -= money;
return true;
}
return false;
}
}
class ThreadTask implements Runnable {
Account account = new Account();
public void run() {
while (account.getBalance() > 0) {
// 100 ~ 300원의 인출금을 랜덤으로 정합니다.
int money = (int) (Math.random() * 3 + 1) * 100;
// withdraw를 실행시키는 동시에 인출 성공 여부를 변수에 할당합니다.
boolean denied = !account.withdraw(money);
// 인출 결과 확인
// 만약, withraw가 false를 리턴하였다면, 즉 인출에 실패했다면,
// 해당 내역에 -> DENIED를 출력합니다.
System.out.println(String.format("출금 %d₩ By %s. 잔액 : %d %s",
money, Thread.currentThread().getName(), account.getBalance(), denied ? "-> 거부" : "")
);
}
}
}
// 출력
출금 100₩ By 이자바. 잔액 : 700
출금 200₩ By 김이룸. 잔액 : 700
출금 200₩ By 이자바. 잔액 : 500
출금 200₩ By 김이룸. 잔액 : 300
출금 300₩ By 이자바. 잔액 : 0
출금 300₩ By 김이룸. 잔액 : -300
출력 결과를 보면 정상적이지 않다
- 인출금과 잔액이 정상적이지 않다
- if (balance >= money) 조건문이 무시된 것처럼 음수 잔액이 발생한다
- -> 거부 가 제대로 출력되지 않는다
이는 두 스레드 간에 객체가 공유되기 때문에 발생하는 오류이다.
이러한 상황이 발생하지 않게 하기 위해 스레드 동기화를 사용한다
임계 영역(Critical section)과 락(Lock)
- 임계 영역 : 오직 하나의 스레드만 코드를 실행할 수 있는 코드 영역
- 락 : 임계 영역을 포함하고 있는 객체에 접근할 수 있는 권한
- 임계 영역으로 설정된 객체가 다른 스레드에 의해 작업이 이루어지지 않을 때, 임의의 스레드 A는 해당 객체에 대한 락을 획득하여 임계 영역 내의 코드를 실행할 수 있다
- 스레드 A가 임계영역임계 영역 내의 코드를 실행 중일 때, 다른 스레드들은 락이 없으므로 임계 영역 내의 코드를 실행할 수 없음
- 스레드 A가 코드를 모두 실행한 뒤 락을 반납하면 다른 스레드들 중 하나가 락을 획득하여 임계 영역 내의 코드를 실행
오류를 막기 위해서
synchronized 키워드를 사용
임계영역을 설정해주어서 두 스레드가 동시에 실행하면 안 되는 구간을 설정해준다
1. 메서드 전체를 임계 영역으로 지정
public synchronized boolean withdraw(int money) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (Exception error) {}
balance -= money;
return true;
}
return false;
}
2. 특정 영역을 임계 영역으로 지정
public boolean withdraw(int money) {
synchronized (this) { // 임계 영역 지정
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (Exception error) {}
balance -= money;
return true;
}
return false;
}
}
728x90
'개발일지 > Java' 카테고리의 다른 글
Java Virtual Machine (자바 가상 머신) (0) | 2022.09.25 |
---|---|
Java 스레드 상태와 실행 제어 메서드 (0) | 2022.09.25 |
Java 스레드 이름 조회 및 설정 (0) | 2022.09.24 |
Java 스레드 (Thread) (0) | 2022.09.24 |
Java File (0) | 2022.09.24 |