* JAVA - 쓰레드 (10)

jodbsgh·2022년 4월 25일
0

💡"JAVA"

목록 보기
53/67

wait()과 notify

  • 동기화의 효율을 높이기 위해 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()
    
}

wait()과 notify() - 예제1 (동기화X)

  • 요리사는 Table에 음식을 추가. 손님은 Table의 음식을 소비
  • 요리사와 손님이 같은 객체(Table)을 공유하므로 동기화가 필요
/* ----------------------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개 발생
  1. 요리사가 Table에 요리를 추가하는 과정에 손님이 요리를 먹음
  2. 하나 남은 요리를 손님2가 먹으려하는데, 손님1이 먹음

동기화된 Table (비효율적인 동기화o),

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을 재 획득)

예제2

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);
    }
}
profile
어제 보다는 내일을, 내일 보다는 오늘을 🚀

0개의 댓글