- 여러 개의 쓰레드가 생성되었을 때 하나의 자원을 이용하는 상황은 자주 발생. 공유자원을 이용할 때 문제가 발생하게 되는
근본적인 이유는 동시에 작업을 진행하는 쓰레드의 특성 때문.
- 공유자원이 문제가 되는 이유
-> 동시에 작업을 진행하는 쓰레드의 특성 때문
- Bank라는 클래스의 객체를 NotSynMain에서 스태틱으로 생성함으로써 myBank객체는 프로그램의 하나의 메모리만 생성.
-> Bank 클래스
class Bank{
// ...
}
-> NotSynMain 클래스의 static 멤버
class NotSynMain{
public static Bank myBank = new Bank();
// ... 작업
}
- 그리고 다음과 같이 두 개의 쓰레드에서 NotSynMain의 myBank객체를 이용한다면 myBank는 공유자원이 됨.
-> Park 쓰레드 클래스
class Park extends Thread{
public void run(){
// NotSynMain.myBank 사용
}
}
-> ParkWife 쓰레드 클래스
class ParkWife extends Thread{
public void run(){
// NotSynMain.myBank 사용
}
}
-> 두 개의 쓰레드가 같이 동시에 실행된다면 공유자원의 문제가 발생.
==========================================================================================================
public class Bank {
private int money = 10000; // 예금 잔액
public int getMoney(){
return this.money;
}
public void setMoney(int money){
this.money = money;
}
public void saveMoney(int save){
int m = this.getMoney();
try{
Thread.sleep(3000);
}catch (Exception e) {
// TODO: handle exception
}
this.setMoney(m + save);
}
public void minusMoeny(int minus){
int m = this.money;
try{
Thread.sleep(2000);
}catch (Exception e) {
// TODO: handle exception
}
this.setMoney(m - minus);
}
}
public class Park extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
NotSyncMain.myBank.saveMoney(3000);
System.out.println("minusMoney(3000) : " + NotSyncMain.myBank.getMoney());
}
}
public class ParkWife extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
NotSyncMain.myBank.minusMoeny(1000);
System.out.println("minusMoney(1000) : " + NotSyncMain.myBank.getMoney());
}
}
public class NotSyncMain {
public static Bank myBank = new Bank();
public static void main(String[] args) {
Park p = new Park();
ParkWife w = new ParkWife();
p.start();
try{
Thread.sleep(200);
}catch (Exception e) {
// TODO: handle exception
}
w.start();
}
}
============================================================================================================
- 공유자원 Bank myBank의 이용
-> NotSyncMain.myBank.saveMoney(3000); // 3000원 입금
-> NotSyncMain.myBank.minusMoney(1000); // 1000원 출금
- Bank myBank객체는 통장과 같은 역할을 하며, myBank는 기본적으로 10000원이 예금된 상태. 그리고 Park의 쓰레드에서 3000원을 입금하려고 하며, ParkWife 쓰레드에서 1000원을 출금하려 함. 이 때 입금을 위해서는 3초의 시간이 걸리며, 출금을 위해서는 0.2초의 시간이 소요. 일반적인 계산으로는 다음과 같이 계산되어야 함.
-> 동기화가 보장된 상태에서의 입출금 금액
=> 12000(결과금액) = 10000(원금) + 3000(입금) - 1000(출금)
-> 하지만 위의 예제의 실행결과는 13000원이 출력됨. 그 이유는 Park의 run()에서 예금된 금액을 가져온 후 3초의 시간이 지연된 후 입금이 처리되기 때문.
-> public void saveMoney(int save){
int m = this.getMoney(); // 예금되어 있는 금액 확인
try{
Thread.sleep(3000);
}catch(InterruptedException e){e.printStackTrace();}
this.setMoney(m + save); // 입금처리
- 3초의 시간이 지연되는 동안 다른 곳에서 작업이 이루어진다면 문제가 발생할 것. 즉, 출금할 때 minusMoney()에서 0.2초의 시간으로 출금작업이 중간에 처리되어 버리기 때문에 계산은 다음의 절차대로 됨.
-> 동기화가 유지되지 않은 경우 입출금의 계산 절차
=> Park이 10000을 읽어감
=> Park는 3초 대기
=> Park이 대기하는 동안 ParkWife 또한 10000을 읽어감
=> Park이 대기하는 동안 ParkWife 0.2초 대기
=> Park이 대기하는 동안 ParkWife는 1000원을 출금
=> Park이 대기하는 동안 ParkWife는 작업완료(남은 돈은 9000원)
=> Park은 3초 대기한 후 읽어온 10000으로 3000원 입금
=> 결과는 13000원
-> 동기화의 유지
=> Park이 Bank myBank를 사용할 때 ParkWife는 Bank myBank를 사용하기 위해서 대기해야 함.
- 즉 Park p가 Bank myBank를 사용하고 있을 때 다른 곳에서 myBank를 사용하려 한다면 다른 쓰레드는 대기하도록 만들어야함.
위의 예에서 실제 공유자원은 Bank의 money임. 즉 Bank myBank의 멤버 변수인 money의 메모리를 락(Lock)으로 봉쇄한다면 다른 쓰레드는 이를 이용하지 못할 것. 공유자원에 락(Lock)을 걸기 위해서 synchronized 키워드를 이용하면 됨.