Notice
Recent Posts
Recent Comments
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

충분히 쌓여가는

wait()와 notify() 본문

Java/JAVA3

wait()와 notify()

빌드이너프 2023. 8. 8. 11:08

 

wait()와 notify()

동기화의 효율을 높이기 위해 wait(), notify()를 사용(한 번에 한 쓰레드만 임계영역에 들어갈 수 있기 때문)

Object 클래스에 정의되어 있으며, 동기화 블록 내에서만 사용할 수 있다

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

 

 

코드

요리사는 Table에 음식을 추가, 손님은 Table의 음식을 소비

요리사와 손님이 같은 객체(Table)를 공유하므로 동기화가 필요

 

동기화되지 않을경우

예외 1: 요리사가 Table에 요리를 추가하는 과정에 손님이 요리를 먹음

예외 2: 하나 남은 요리를 손님2가 먹으려하는데, 손님1이 먹음

문제점: Table을 여러 쓰레드가 공유하기 때문에 작업 중에 끼어들기 발생

해결책: Table의 add()와 remove()를 synchronized로 동기화

import java.util.ArrayList;

class Customer implements Runnable {
    private Table  table;
    private String food;

    Customer(Table table, String food) {
        this.table = table;
        this.food  = food;
    }

    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
    }

    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) {}
        } // while
    }
}

class Table {
    String[] dishNames = { "donut","donut","burger" };
    final int MAX_FOOD = 6;
    private ArrayList<String> dishes = new ArrayList<>();
    public void add(String dish) {
        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+" is waiting.");
                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;
                }
//        } // synchronized

        return false;
    }

    public int dishNum() { return dishNames.length; }
}

class synchronizationTest {
    public static void main(String[] args) throws Exception {
        Table table = new Table(); // 여러 쓰레드가 공유하는 객체

        new Thread(new Cook(table), "COOK").start();
        new Thread(new Customer(table, "donut"),  "CUST1").start();
        new Thread(new Customer(table, "burger"), "CUST2").start();

        Thread.sleep(5000);
        System.exit(0);
    }
}

 

 

 

 

동기화는 되는데 작업진행이 잘안됨(동기화의 비효율성)

문제점: 음식이 없을 때, 손님이 Table의 lock를 쥐고 안놓는다, 요리사가 lock를 얻지 못해서 Table에 음식을 추가할 수 없다

해결책: 음식이 없을 때, wait()로 손님이 lock를 풀고 기다리게 해야한다, 요리사가 음식을 추가하면, notify()로 손님에게 알린다(손님이 lock를 재획득)

import java.util.ArrayList;

class Customer implements Runnable {
    private Table  table;
    private String food;

    Customer(Table table, String food) {
        this.table = table;
        this.food  = food;
    }

    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
    }

    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) {}
        } // while
    }
}

class Table {
    String[] dishNames = { "donut","donut","burger" };
    final int MAX_FOOD = 6;
    private ArrayList<String> dishes = new ArrayList<>();
    public synchronized void add(String dish) { // synchronized를 추가
        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+" is waiting.");
                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;
                }
        } // synchronized

        return false;
    }

    public int dishNum() { return dishNames.length; }
}

class synchronizationTest {
    public static void main(String[] args) throws Exception {
        Table table = new Table(); // 여러 쓰레드가 공유하는 객체

        new Thread(new Cook(table), "COOK").start();
        new Thread(new Customer(table, "donut"),  "CUST1").start();
        new Thread(new Customer(table, "burger"), "CUST2").start();

        Thread.sleep(5000);
        System.exit(0);
    }
}

 

최종코드

import java.util.ArrayList;

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) {}
            String name = Thread.currentThread().getName();

            table.remove(food);
            System.out.println(name + " ate a " + food);
        } // while
    }
}

class Cook2 implements Runnable {
    private Table2 table;

    Cook2(Table2 table) { this.table = table; }

    public void run() {
        while(true) {
            int idx = (int)(Math.random()*table.dishNum());
            table.add(table.dishNames[idx]);
            try { Thread.sleep(10);} 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(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) {
        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++) {
                    if(dishName.equals(dishes.get(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 synchronizationTest {
    public static void main(String[] args) throws Exception {
        Table2 table = new Table2();

        new Thread(new Cook2(table), "COOK").start();
        new Thread(new Customer2(table, "donut"),  "CUST1").start();
        new Thread(new Customer2(table, "burger"), "CUST2").start();
        Thread.sleep(2000);
        System.exit(0);
    }
}

 

'Java > JAVA3' 카테고리의 다른 글

함수형 인터페이스  (0) 2023.08.09
람다식(Lambda Expression)  (0) 2023.08.09
쓰레드의 동기화(synchronization)  (0) 2023.08.07
join(), yield()  (0) 2023.08.07
suspend(), resume(), stop()  (0) 2023.08.07