동기화의 효율을 높이기 위해 wait(), notify()를 사용
Object클래스에 정의되어 있으며, 동기화 블록 내에서만 사용할 수 있다.
wait()
객체의 lock을 풀고 쓰레드를 해당 객체의 waiting pool에 넣는다.
notify()
waiting pool에서 대기중인 쓰레드 중의 하나를 깨운다.
notifyAll()
waiting pool에서 대기중인 모든 쓰레드를 깨운다.
일반적으로 그냥 notify()를 사용하는 것 보다는 notifyAll()을 쓰는게 더 좋다.
class Account
{
int balance = 1000;
public synchronized void withdraw(int money)
{
while(balance<money)
{
try
{
wait(); // 대기 - 락을 풀고 기다린다.
// 통지를 받으면 락을 재획득(ReEntrance)
} catch (InterruptedException e) { }
}
balance -= money;
}//waithdraw()
public synchronized void deposit(int money )
{
balance += money;
notify(); //통지 - 대기중인 쓰레드 중 하나에게 알림.
}//deposit()
}
/* ----------------------Table */
private ArrayList dishes = new ArrayList();
// Table에 dish(음식)을 추가하는 메서드
public void add (String dish)
{
if(dishes.size() >= MAX_FOOD)
return;
dishes.add(dish);
System.out.println("Dishes :" + dishes.toString());
}
//Table에 dish(음식)을 삭제하는 메서드
public boolean remove(String dishName)
{
for(int i=0; i<dishes.size(); i++)
{
dishes.romove(i);
return true;
}
return false;
}
/* ----------------------Cook */
//요리사는 Table에 음식(dish)을 추가하는 일을 한다.
public void run()
{
while(true)
{
int idx= (int)(Math.random() * table.dishNum());
table.add(table.dishNames[idx]);
try
{
Thread.sleep(1);
} catch (InterruptedException e) { }
}// while
}
/* ----------------------Customer */
//손님은 Table의 음식(dish)을 먹는 일을 한다.
public void run ()
{
while(true)
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e) { }
String name = Thread.currentThread().getName();
if(eatFood())
System.out.println(name + "ate a " + food);
else
System.out.println(name + "failed to eat. : (" );
}//while
}
bolean eatFood() { return table.remove(food); }
/* ----------------------main */
Table table = new Table(); //여러 쓰레드가 공유하는 객체
new Thread(new Cook(table), "COOKI".start();
new Thread(new Customer(table, "donut"),"CUST1").start();
new Thread(new Customer(table, "burger"), "CUST2").start();
- 문제점
예외 2개 발생
- 요리사가 Table에 요리를 추가하는 과정에 손님이 요리를 먹음
- 하나 남은 요리를 손님2가 먹으려하는데, 손님1이 먹음
public synchronized void add(String dish){
if(dishes.size() >= MAX_FOD)
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 + "is waiting. ");
try
{
Thread.sleep(500);
} catch(InterruptedException e) { }
}
for(int i=0; i<dishes.size(); i++)
{
dishes.remove(i);
return true;
}
}//syncronized
return false;
}
- 문제점
음식이 없을 때, 손님이 Table의 lock을 쥐고 안놓는다.
요리사가 lock을 얻지 못해서 Table에 음식을 추가할 수 없다.- 해결책
음식이 없을 때, wait()으로 손님이 lock을 풀고 기다리게 하자.
요리사가 음식을 추가하면, notify()로 손님에게 알리자.(손님이 lock을 재 획득)
public synchronized void add(String dish)
{
//음식이 꽉찾을 때 요리사 재우기
while(dishes.size() >= MAX_FOOD)
{
String name = Thread.currentThread().getName();
System.out.println(name+ "is waiting.");
try
{
wait(); //COOK쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch (InterruptedException e) { }
}
dishes.add(dish);
notify(); // 음식이 추가되면 기다리고 있는 CUST를 꺠운다.
System.out.println("Dishes" + dishes.toString());
}
public void remove(String dishName)
{
syncronized(this){
String name = Thread.currentThread().getName();
while(dishes.size()==0)
{
System.out.println(name + "is waiting.");
try
{
wait(); //CUST 쓰레드를 기다리게 한다.
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(); //잠자고있는 CUST꺠우기
return;
}
}// for문 끝
try
{
System.out.println(name + "is waiting.");
wait(); //원하는 음식이 없는 CUST쓰레드를 기다리게함
Thread.sleep(500);
} catch (InterruptedException e) { }
}// while(true)
}//syncronized
}
- 문제점
누구에게 notify할지 불문명하다- 해결책
Lock & Condition을 사용한다.
예제 정리본)
class Customer2 implements Runnable{
private Table2 table;
private String food;
customer2(Table2 table, String food) {
this.table = table;
this.food = food;
}
public void run() {
while(true) {
try{
Thread.sleep(100);
} catch(InterruptedException e) { }
table.remove(food);
System.out.println(name + "ate a " + food);
}//while
}
}
class Cook2 implements Runnable {
private Table table;
Cook2(Table table) { this.table = table }
public void run() {
while(true)
{
int idx = (int)(Math.random() * table.dishNum());
table.add(table.dishName[idx]);
try
{
Thread.sleep(100);
} catch (InterruptedException e) { }
}// while
}
}
class Table2 {
String[] dishNames = {"donut", "donut", "burger" } //donut의 확률이 더 높음
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<>();
public synchronized void add (String dish)
{
while(dish.size() >= MAX_FOOD)
{
String name = Thread.currentThread().getName();
System.out.println(name + "is waiting");
try
{
wait(); //COOK쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch (InterruptedException e) { }
}
dishes.add(dish);
notify() //기다리고 있는 CUST 꺠움
System.out.println("Dishes:" + dishes.toString());
}
public void remove(String dishName)
{
synchronized(this)
{
String name = Thread.currentThread().getName();
while(dishes.size() ==0)
{
System.out.println(name + "is waiting");
try
{
wait(); //CUST쓰레드를 기다리게함
Thread.sleep(500);
} catch ( InterruptedException e) { }
}
while(true)
{
for(int i=0; i<dishes.size(); i++)
{
dishes.remove(i);
notify();//자고있는 COOK 꺠움
return;
}//for문 끝
try
{
System.out.println(name + "is waiting");
wait(); //원하는 음식이 없는 CUST 재움
Thread.sleep(500);
} catch(InterruptedException e) { }
}// while(true)
}//synchronized
}
public int dishNum() { return dishNames.length; }
}
class Test
{
public static void main(String[] args) throws Exception
{
Table2 table = new Table2();
new Thread(new Cook2(table), "COOK").start();
new Thread(new Customer(table, "donut"), "Cust1").start();
new Thread(new Customer(table, "burger"), "Cust2").start();
Thread.sleep(2000);
System.exit(0);
}
}