wait() 기다리기, notify() 통보·알려주기

 

- 동기화의 효율을 높이기 위해 wait(), notify()를 사용

- Object클래스에 정의되어 있으며, 동기화 블록 내에서만 사용 가능

 

  • wait() - 객체의 lock을 풀고 스레드를 해당 객체의 waiting pool에 넣는다.
  • notify() - waiting pool에서 대기중인 스레드 중의 하나를 깨운다.
  • notifyAll() - waiting pool에서 대기중인 모든 스레드를 깨운다.
// 은행
class Account3{
	private int balance = 1000;
	
	public synchronized void withdraw(int money) {
		while(balance < money) {
			try {
				wait(); // 대기 - 락을 풀고 기다린다. 통지를 받으면 락을 재획득(ReEntrace)
			} catch (InterruptedException e) {}
		}
		balance -= money;
	}
	
	public synchronized void deposit(int money) {
		balance += money;
		notify(); // 통지 - 대기중인 쓰레드 중 하나에게 알림. ( 하나 ? )
	}
	
}

wait()을 쉽게 말하자면 출금할려는 돈이 은행에 있는 돈 보다 많으면 계속 해서 while문에 있어야 하는데

 

wait() 메서드를 이용해서 락을 풀고 기다린다. ( 원래는 다른 스레드 접근 불가능한 상태 잠시 휴식 룸으로 이동 )

 

그리고 은행 잔고에 돈이 추가되면 notify(); 메서드로 휴식 룸에 있는 한 스레드에게 알려준다. 그러면 다시

 

wait() 메서드가 락을 다시 얻고 출금 가능한 금액이면 출금을 실행하고 아니면 또 휴식룸에 가서 은행 잔고에 돈이 추가될 때 까지 기다린다.


요리 예제

package ThreadPt;

import java.util.ArrayList;


public class ThreadCook {
	public static void main(String[] args) throws Exception{ //예외 발생하니깐 던질거임
		Table table = new Table(); // 여러 쓰레드가 공유하는 객체 테이블
		
		new Thread(new Cook(table), "COOK").start();
		new Thread(new Customer(table,"도넛"), "손님1").start();
		new Thread(new Customer(table,"햄버거"), "손님2").start();
		
		Thread.sleep(5000);
		System.exit(0);
	}
}

class Customer implements Runnable{
	private Table table;
	private String food;
	
	Customer(Table table, String food) {
		this.table = table;  
		this.food  = food;
	}
	
	@Override
	public void run() {
		while(true) {
			try { Thread.sleep(10);} catch(InterruptedException e) {}
			String name = Thread.currentThread().getName();

			if(eatFood())
				System.out.println(name + " 음식을 먹었습니다 " + food);
			else 
				System.out.println(name + " 음식먹기에 실패했습니다 ㅠㅠ ");
		}
		
	}
	boolean eatFood() { return table.remove(food); }
}

class Cook implements Runnable{
	private Table table;

	Cook(Table table) {	this.table = table; }

	public void run() {
		while(true) {
			int idx = (int)(Math.random()*table.dishNum());
			table.add(table.dishNames[idx]);
			try { Thread.sleep(100);} catch(InterruptedException e) {}
		}
	}
}

class Table{
	String[] dishNames = {"도넛","초밥","햄버거","도넛"};
	final int MAX_FOOD = 6;
	private ArrayList<String> dishes = new ArrayList();
	
	// 음식을 추가
	public synchronized void add(String dish) { // synchronzied를 추가했음 (임계 영역)
		if(dishes.size() >= MAX_FOOD)
			return;
		dishes.add(dish);
		System.out.println("Dishes :" + dishes.toString());
	}
	
	// 음식을 제거
	public boolean remove(String dishName) {
		synchronized (this) {
			while(dishes.size()==0) {
				String name = Thread.currentThread().getName();
				System.out.println(name+" 기다리는 중..");
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {}
			}
			
			for(int i=0; i<dishes.size(); i++) {
				if(dishName.equals(dishes.get(i))){
					dishes.remove(i);
					return true;
				}
			}
		}
		return false;
	}
	
	public int dishNum() {return dishNames.length;}
}

결과값

 

차근차근 해석해보자.

 

손님 스레드, 요리사 스레드, 테이블을 구현해줬다.

 

손님과 요리사는 table 객체를 이용하고 있다.

 

손님은 eatFood로 table에 있는 요리를 먹어치울 수 있다(remove).

 

그래서 손님의 작업 run() 메서드에는 if(eayFood()) 음식을 먹었더라면 ~ 음식을 먹었고 , 못먹었으면 슬퍼한다.

 

요리사의 run() 메서드에는 idx 변수를 이용한 랜덤으로 { 도넛 , 초밥 , 햄버거 , 도넛 } * 난수(0.0~0.9) 로 음식을 만들어

테이블에 만들어준다.

 

테이블에 놓을 수 있는 음식들은 MAX_FOOD = 6; 6개 까지이다. 이것은 변하지 않아서 상수로 둔다. ( 테이블 크기가 커지진 않으니깐.. )

 

만약 테이블 보다 크면 다시 돌려보내고 , 아니면 음식을 추가한다.

 

이 코드에서 문제점이 있다.

음식이 없을때 휴식 룸에서 기달리거나 해야되는데 계속 그 자리에서 기다리고 있어서 손님1이 기다리는중 .. 이 계속 뜬

 

다. 

 

이걸 해결하려면 음식이 없을때는 손님을 wait()으로 기다리게 해줘야 한다.

 

요리사가 음식을 추가하면, notify()로 손님한테 알려주면 된다. (notifyAll()은 모든 기다리는 쓰레드를 깨움)

 

package ThreadPt;

import java.util.ArrayList;


public class ThreadCook {
	public static void main(String[] args) throws Exception{ //예외 발생하니깐 던질거임
		Table table = new Table(); // 여러 쓰레드가 공유하는 객체 테이블
		
		new Thread(new Cook(table), "COOK").start();
		new Thread(new Customer(table,"도넛"), "손님1").start();
		new Thread(new Customer(table,"햄버거"), "손님2").start();
		
		Thread.sleep(5000);
		System.exit(0);
	}
}

class Customer implements Runnable{
	private Table table;
	private String food;
	
	Customer(Table table, String food) {
		this.table = table;  
		this.food  = food;
	}
	
	@Override
	public void run() {
		while(true) {
			try { Thread.sleep(10);} catch(InterruptedException e) {}
			String name = Thread.currentThread().getName();

			if(eatFood())
				System.out.println(name + " 음식을 먹었습니다 " + food);
			else 
				System.out.println(name + " 음식먹기에 실패했습니다 ㅠㅠ ");
		}
		
	}
	boolean eatFood() { return table.remove(food); }
}

class Cook implements Runnable{
	private Table table;

	Cook(Table table) {	this.table = table; }

	public void run() {
		while(true) {
			int idx = (int)(Math.random()*table.dishNum());
			table.add(table.dishNames[idx]);
			try { Thread.sleep(100);} catch(InterruptedException e) {}
		}
	}
}

class Table{
	String[] dishNames = {"도넛","초밥","햄버거","도넛"};
	final int MAX_FOOD = 6;
	private ArrayList<String> dishes = new ArrayList();
	
	// 음식을 추가
	public synchronized void add(String dish) { // synchronzied를 추가했음 (임계 영역)
		if(dishes.size() >= MAX_FOOD) {
			String name = Thread.currentThread().getName();
			System.out.println(name + " 은 기다리는중 .");
			try {
				wait(); //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
				Thread.sleep(500);
			} catch (InterruptedException e) {}
		}
		dishes.add(dish);
		notify(); //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
		System.out.println("Dishes :" + dishes.toString());
	}
	
	// 음식을 제거
	public boolean remove(String dishName) {
		synchronized (this) {
			String name = Thread.currentThread().getName();
			while(dishes.size()==0) {
				System.out.println(name+" 기다리는 중..");
				try {
					wait(); //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
					Thread.sleep(500);
				} catch (InterruptedException e) {}
			}
			
			while (true) {
				for (int i = 0; i < dishes.size(); i++) {
					if (dishName.equals(dishes.get(i))) {
						dishes.remove(i);
						notify(); // ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
						return true;
					}
				}

				try {
					System.out.println(name+" 기다리는 중...");
					wait(); //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
					Thread.sleep(500);
				} catch (InterruptedException e) {}
			}
		}
	}
	
	public int dishNum() {return dishNames.length;}
}

★ 모양의 코드로 효율적으로 바꿧다.

 

69line 이것은 요리사(Cook)이 휴식하라는 wait() 메서드이다 테이블에 요리가 꽉 차있어서 못 놓기 때문.

74line notify()는 요리가 추가 되어서 손님에게 알려주는 코드

85line 여기는 손님이 요리가 없어서 잠시 휴식하러 가라는 wait()코드.

94line 손님이 먹으려는 음식과 같은 음식이 있어서 요리사 보고 갔다 놓으라고 깨워주는 notify()코드이다.

101line 원하는 음식이 없다고 계속 테이블에서 대기하는 손님보고 쉬라고하는 wait()코드이다.

 

하지만 notify()메서드는 휴식 하고 있는 스레드 중 하나를 깨우기 때문에 요리사를 깨워야 하는데 손님을 깨울수 가 있다.

그래서 종류가 구별되는 lock and condition 이 나왔다. 

+ Recent posts