열심히 끝까지

Java2 day16 본문

Java2(주말)

Java2 day16

노유림 2022. 5. 29. 17:48

[16일차 수업내용]

            7-2 ) interrupt()와 interrupted() - 쓰레드의 작업을 취소한다.
                        - 진행 중인 쓰레드의 작업이 끝나기 전에 취소시켜야 할 때가 있음.
                        - 예를 들어 큰 파일을 다운로드 받을 때 시간이 너무 오래걸리면 중간에 다운로드를
                          포기하고 취소할 수 있어야 함.
                        - interrupt()쓰레드에게 작업을 멈추라고 요청.
                        - 단지 멈추라고 요청만 하는 것일 뿐 쓰레드를 강제로 종료시키지는 못함.
                                    ==> 그저 쓰레드의 interrupted 상태( 인스턴스 변수 )를 바꾸는 것.
                                                => interrupted : boolean값
                        - 그리고 interrupted()쓰레드에 대해 interrupt()가 호출되었는지 알려줌.
                        - interrupt()가 호출되지 않았다면 false를, interrupt()가 호출되었다면 true를 반환
                        - interrupt()가 호출되면 interrupted()의 결과가 false에서 true로 바뀌어 이를 이용하여
                          쓰레드를 멈출 수 있는 것.

                                    void interrupt()                     : 쓰레드의 interrupted() 상태를 false에서 true로 변경
                                    boolean isInterrupted()           : 쓰레드의 interrupted() 상태를 반환  
                                    static boolean interrupted()      : 현재 쓰레드의 interrupted() 상태를 반환 후 false로 변경

                        - 쓰레드가 sleep(), wait(), join()에 의해서 일시정지상태(WAITING)에 있을 때
                          해당 쓰레드에 interrupt() 호출하면 sleep(), wait(), join()에서 InterruptedException이 발생하고
                          쓰레드는 실행대기상태(RUNNABLE)로 바뀜.
                                    ==> 즉, 멈춰있떤 쓰레드를 개워서 실행 가능한 상태로 만드는 것.

            7-3 ) suspend(), resume(), stop()
                        - suspend()는 sleep()처럼 쓰레드를 멈추게 함.
                        - suspend()에 의해 정지된 쓰레드resume()을 호출해야 다시 실행대기상태가 됨.
                        - stop()은 호출되는 즉시 쓰레드가 종료됨.

                        - suspend(), resume(), stop()은 쓰레드의 실행을 제어하는 가장 손쉬운 방법이지만,
                          suspend()와 stop()이 교착상태(deadlock)을 일으키기 쉽게 작성되어 있으므로
                          사용이 권장되지 않음.
                                    * 교착상태 : 두 개 이상의 작업이 서로 상대방의 작업이 끝나기만을 기다리고 있기 때문
                                                    아무것도 완료되지 않은 상태를 가리킴.

            7-4 ) yield() - 다른 쓰레드에게 양보
                        - yield()는 쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보함.
                        - 예를 들어 스케쥴러에 의해 1초의 실행시간을 할당받는 쓰레드가 0.5초의 시간동안 

                          작업한 상태에서 yield가 호출되면 나머지 0.5는 포기하고 다시 실행대기 상태가 됨.
                        - yield와 interrupt()를 적절히 사용하면, 프로그램의 응답성을 높이
                          보다 효율적인 실행이 가능하게 할 수 있음.
                        
            7-5 ) join() 다른 쓰레드의 작업을 기다림
                        - 쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 할 때
                          join()을 사용
                        
                                    void join()
                                    void join(long millis)
                                    void join(long millis, int nanos)

                        - 시간을 지정하지 않으면 쓰레드가 작업을 모두 마칠 때까지 기다리게 됨.
                        - 작업 중 다른 쓰레드의 작업이 먼저 수행되어야 할 필요가 있을 때 join()을 사용
                        - join()도 sleep()처럼 interrupt()에 의해 대기상태에서 벗어날 수 있으며
                          join()이 호출되는 부분은 try-catch문으로 감싸야 함
                        - join()은 여러보로 sleep()과 유사한 점이 많은데 sleep()과 다른 점은 join()은 현재 쓰레드가 아닌
                          특정 쓰레드에 대해 동작하므로 static 메소드가 아니라는 점

-------------------------------------------------------------------
package example01;

import javax.swing.JOptionPane;

public class ThreadEx09 {
            public static void main(String[] args) {
                        ThreadEx09_1 th1 = new ThreadEx09_1();
                        th1.start();

                        String input = JOptionPane.showInputDialog("아무값이나 입력하세요");
                        System.out.println("입력하신 값 : " + input);

                        th1.interrupt();
                        System.out.println("main쓰레드에서의 th1.interrupted() : " + th1.isInterrupted());
            }
}
class ThreadEx09_1 extends Thread{
            @Override
            public void run() {
                        int i = 10;

                        while(i != 0 && !isInterrupted()) {
                                    System.out.println("i : " + (i--));
                                    for(long x = 0; x < 1500000000000000000L; x++); // 0이 17개 시간 지연
                                    System.out.println("!isInterrupted() : " + !isInterrupted());
                        }
                        System.out.println("카운트가 종료되었습니다.");
            }
}

----------------------------------------
package example01;

import javax.swing.JOptionPane;

public class ThreadEx10 {
            public static void main(String[] args) {
                        ThreadEx10_1 th1 = new ThreadEx10_1();
                        th1.start();

                        String input = JOptionPane.showInputDialog("아무값이나 입력하세요.");
                        System.out.println("입력하신 값 : " + input);

                        th1.interrupt();
                        System.out.println("main쓰레드에서의 isInterrupted() 출력 : " + th1.isInterrupted());
            }
}

class ThreadEx10_1 extends Thread{
            @Override
            public void run() {
                        int i  = 10;

                        while( i != 0 && !isInterrupted()) {
                                    System.out.println("i : " + (i--));
                                    try {
                                                Thread.sleep(1000);
                                                System.out.println("!isInterrupted() : " + !isInterrupted());
                                                // sleep()으로 깨어난 쓰레드는 interrupted의 상태를 false로 초기화시킨다.
                                    } catch(InterruptedException e){}
                        }
                        System.out.println("카운트가 종료되었습니다.");
            }
}

----------------------------------------
package example01;

public class ThreadEx11 {
            public static void main(String[] args) {
                        // suspend(), resume(), stop() ==> 교착상태를 일으킬 가능성이 높다.
                        Runnable r = new RunImplEx11();
                        Thread th1 = new Thread(r, "*");
                        Thread th2 = new Thread(r, "**");
                        Thread th3 = new Thread(r, "***");

                        th1.start();
                        th2.start();
                        th3.start();

                        try {
                                    Thread.sleep(2000);
                                    th1.suspend();
                                    Thread.sleep(2000);
                                    th2.suspend();
                                    Thread.sleep(3000);
                                    th3.resume();
                                    Thread.sleep(3000);
                                    th1.stop();
                                    th2.stop();
                                    Thread.sleep(2000);
                                    th3.stop();
                        }catch(Exception e) {}
            }
}

class RunImplEx11 implements Runnable{
            @Override
            public void run() {
                        while(true) {
                                    System.out.println(Thread.currentThread().getName());
                                    try {
                                                Thread.sleep(1000);
                                    }catch (InterruptedException e) {}
                        }
            }
}

----------------------------------------
package example01;

public class ThreadEx12 {
            public static void main(String[] args) {
                        RunImpleEx12 r1 = new RunImpleEx12();
                        RunImpleEx12 r2 = new RunImpleEx12();
                        RunImpleEx12 r3 = new RunImpleEx12();

                        Thread th1 = new Thread(r1, "*");
                        Thread th2 = new Thread(r2, "**");
                        Thread th3 = new Thread(r3, "***");

                        th1.start();
                        th2.start();
                        th3.start();

                        try {
                                    Thread.sleep(2000);
                                    r1.suspend();
                                    Thread.sleep(2000);
                                    r2.suspend();
                                    Thread.sleep(3000);
                                    r1.resume();
                                    Thread.sleep(3000);
                                    r1.stop();
                                    r2.stop();
                                    Thread.sleep(2000);
                                    r3.stop();
                        }catch (Exception e) {}
            }
}

class RunImpleEx12 implements Runnable{

            volatile boolean suspended = false;
            volatile boolean stopped = false;
            // volatile : suspend(), resume(), stop()을 재정의 할 때 필요한 제어자

            @Override
            public void run() {
                        while(!stopped) {
                                    if(!suspended) {
                                                System.out.println(Thread.currentThread().getName());
                                                try {
                                                            Thread.sleep(1000);
                                                }catch(InterruptedException e) {}
                                    }
                        }
                        System.out.println(Thread.currentThread().getName() + " - stopped");
            }

            public void suspend() { suspended = true; }
            public void resume() { suspended = false; }
            public void stop() { stopped = true; }
}

------------------------------------------
package example01;

public class ThreadEx13 {
            public static void main(String[] args) {
                        ThreadEx13_1 th1 = new ThreadEx13_1();
                        ThreadEx13_2 th2 = new ThreadEx13_2();

                        th1.start();
                        th2.start();

                        try {
                                    th1.join(); // 현재 실행중인 쓰레드가 th1이 끝날때까지 기다린다.
                                    th2.join(); // 현재 실행중인 쓰레드가 th2가 끝날때까지 기다린다.
                        }catch (Exception e) {}

                        System.out.println("[main 종료]");
            }
}

class ThreadEx13_1 extends Thread{
            @Override
            public void run() {
                        for(int i = 0; i < 300; i++) {
                                    System.out.print(new String("-"));
                        }
            }
}

class ThreadEx13_2 extends Thread{
            @Override
            public void run() {
                        for(int i = 0; i < 300; i++) {
                                    System.out.print(new String("|"));
                        }
            }
}

------------------------------------------
package example01;

public class ThreadEx14 {
            public static void main(String[] args) {
                        ThreadEx14_1 gc = new ThreadEx14_1();
                        gc.setDaemon(true);
                        gc.start();

                        int requiredMemory = 0;
                        for(int i = 0; i < 20; i++) {
                                    requiredMemory = (int)(Math.random() * 10) * 20;
                                    // 필요한 메모리가 사용할 수 있는 양보다 크거나, 

                                    // 전체 메모리의 60% 이상을 사용했을 경우 gc를 깨운다.
                                    if(gc.freeMemory() < requiredMemory || gc.freeMemory() < gc.totalMemory() * 0.4) {
                                                gc.interrupt();
                                    }

                                    gc.usedMemory += requiredMemory;
                                    System.out.println("usedMemory : " + gc.usedMemory);
                        }
            }
}

class ThreadEx14_1 extends Thread{
            final static int MAX_MEMORY = 1000;
            int usedMemory = 0;

            public void gc() { // 가비지 컬렉터를 수행하는 메소드 
                        usedMemory -= 300;
                        if(usedMemory < 0) {
                                    usedMemory =0;
                        }
            }

            // 최대 사용할 수 있는 메모리 양을 반환하는 메소드
            public int totalMemory() { return MAX_MEMORY; }

            // 현재 사용할 수 있는 메모리 양을 반환하는 메소드
            public int freeMemory() { return MAX_MEMORY - usedMemory; }

            @Override
            public void run() {
                        while(true) {
                                    try {
                                                Thread.sleep(10 * 1000); // 10초
                                    }catch (Exception e) {
                                                System.out.println("Awaken by interrupt()");
                                    }
                                    gc();
                                    System.out.println("Garbage Collected. Free Memory : " + freeMemory());
                        }
            }
}

------------------------------------------
package example01;

public class ThreadEx15 {
            public static void main(String[] args) {
                        Runnable r = new RunnableEx15();

                        new Thread(r).start();
                        new Thread(r).start();

                        /*
                         *  Thread th1 = new Thread(r);
                         *  th1.start();
                         */
            }
}
 
class Account{ // 계좌클래스

            private int balance = 1000;

            // alt + shift + s + r
            public int getBalance() {
                        return balance;
            }

            public void widthrew(int money) {
                        if(balance >= money) {
                                    try {
                                                Thread.sleep(1000);
                                    }catch(Exception e) {}
                                    balance -= money;
                        }
            }
}

class RunnableEx15 implements Runnable{

            Account acc = new Account();

            @Override
            public void run() {
                        while(acc.getBalance() > 0) {
                                    int money = (int)(Math.random() * 3 + 1) * 100;
                                    acc.widthrew(money);
                                    System.out.println("balance : " + acc.getBalance());
                        }
            }
}

-------------------------------------------------------------------
8. 쓰레드의 동기화
            - 싱글쓰레드 프로세스의 경우 프로세스 내에서 단 하나의 쓰레드만 작업하기 때문에
              프로세스의 자원을 가지고 작업하는데 별 문제가 없지만,
              멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서
              작업하기 때문에 서로의 작업에 영향을 주게 됨
            - 만약 쓰레드 A가 작업하던 도중에 다른 쓰레드 B에게 제어권이 넘어갔을 때,
              쓰레드 A가 작업하던 공유데이터를 쓰레드 B가 임의로 변경하였다면
              다시 쓰레드 A가 제어권을 받아서 작업을 마쳤을 때는 원래 의도했던 것과는 다른 결과를
              얻을 수 있음
            - 이러한 일이 발생하는 것을 방지하기 위해서 한 쓰레드가 특정 작업을 끝 마치기 전까지는
              다른 쓰레드에 의해 방해받지 않도록 하는 것이 필요
                        ==> 그래서 도입된 개념이 바로 임계영역(critical section)과 잠금(lock)
            - 공유데이터를 사용하는 코드 영역을 임계영역으로 지정해 놓고, 공유데이터(객체)가 
              가지고 있는 lock을 획득한 단 하나의 쓰레드 이 영역 내의 코드를 수행할 수 있게 됨
            - 이처럼 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 하는 것
              '쓰레드의 동기화(synchronization)'이라고 함

            8-1 ) synchronized를 이용한 동기화
                        - 먼저 가장 간단한 동기화 방법인 synchronized 키워드를 이용한 동기화 방법에는
                          아래와 같은 2가지 방식이 있음

                        ① 메소드 전체를 임계영역으로 지정
                                    public synchronized void withdraw(){}

                        ② 특정한 영역을 임계영역으로 지정
                                    synchronized(객체의 참조변수){

                                    }

                        - 첫번째 방법(메소드 전체)은 메소드 앞에 synchronized를 붙이는 것인데 synchronized를 붙이면
                          메소드 전체가 임계영역으로 지정
                        - 쓰레드는 synchronized 메소드가 호출되는 시점부터 해당 메소드가 포함된 객체의 lock을
                          얻어 작업을 수행하다가 메소드가 종료되면 lock을 반환

                        - 두번째 방법(특정한 영역)은 메소드 내의 코드 일부를 블록으로 감싸고 블록 앞에 

                          synchronized(참조변수)를 붙이는 것인데, 이 때 '참조 변수'는 lock을 걸고자 하는 객체를 

                          참조하는 것이여야 함
                        - 이 블록은 synchronized 블록이라 부르며, 이 블록의 영역 안으로 들어가면서 쓰레드는 

                          지정된 객체의 lock을 얻게 되고 이 블록을 벗어나면 lock을 반납

                        - 두 방법 모두 lock을 하나씩 가지고 있으며 
                          해당 객체의 lock을 가지고 있는 쓰레드만 임계영역의 코드를 수행할 수 있음
                        - 그리고 다른 쓰레드들은 lock을 얻을 때까지 기다리게 됨
                        - 임계영역은 멀티쓰레드 프로그램의 성능을 좌우하기 때문에
                          가능하면 메소드 전체를 임계영역으로 설정하는 것보단
                          synchronized 블록으로 임계영역을 최소화해서 보다 효율적인 프로그램이 되도록 노력해야 함

-------------------------------------------------------------------
package example01;

public class ThreadEx15 {
            public static void main(String[] args) {
                        Runnable r = new RunnableEx15();

                        new Thread(r).start();
                        new Thread(r).start();

                        /*
                         *  Thread th1 = new Thread(r);
                         *  th1.start();
                         */
            }
}
 
class Account{ // 계좌클래스

            private int balance = 1000;

            // alt + shift + s + r
            public int getBalance() {
                        return balance;
            }

            public synchronized void widthrew(int money) {
                        if(balance >= money) {
                                    try {
                                                Thread.sleep(1000);
                                    }catch(Exception e) {}
                                    balance -= money;
                        }
            }
}

class RunnableEx15 implements Runnable{

            Account acc = new Account();

            @Override
            public void run() {
                        while(acc.getBalance() > 0) {
                                    int money = (int)(Math.random() * 3 + 1) * 100;
                                    acc.widthrew(money);
                                    System.out.println("balance : " + acc.getBalance());
                        }
            }
}

---------------------------------------
package example02;

import java.util.ArrayList;

public class ThreadWaitEx01 {
            public static void main(String[] args) throws Exception {
                        Table table = new Table();                        // 여러 쓰레드가 공유하는 객체( 요리사, 손님1, 손님2 )

                        new Thread(new Cook(table), "COOK1").start();
                        new Thread(new Customer(table, "donut"), "CUSTOMER_A").start();
                        new Thread(new Customer(table, "burger"),"CUSTOMER_B").start();

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

class Table{ // 음식을 놓는 클래스
            String[] dishName = {"donut", "donut", "burger"};     // donut이 더 자주 나온다.
            static 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) {              // 음식을 가져가는 메소드
                        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 dishName.length; }
}

class Cook implements Runnable{                           // 음식을 추가하는 요리사 쓰레드
            private Table table;

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

            @Override
            public void run() {
                        while(true) {
                                    int idx = (int)(Math.random() * table.dishNum());   // 0, 1, 2
                                    table.add(table.dishName[idx]);

                                    try { Thread.sleep(1); } catch(InterruptedException e) {}
                        }
            }
}

class Customer implements Runnable{                      // 음식을 가져가는 손님 쓰레드
            private Table table;
            private String food;

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

            boolean eatFood() { return table.remove(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 + " ate a " + food);
                                    }else {
                                                System.out.println(name + " failed to eat.");
                                    }
                        }
            }
}

/*
 *  [ 위 코드에서 발생할 수 있는 예외 ]
 *  ConcurrentModificationException
 *  - 요리 쓰레드가 테이블에 음식을 놓는 도중에 손님 쓰레드가 음식을 가져가려 했기 때문에 발생하는 예외
 *  
 *  IndexOutOfBoundsException
 *  - 손님쓰레드가 테이블의 마지막 음식을 가져가려는 도중에 다른 손님쓰레드가 먼저 음식을 낚아채는 경우
 * 
 */

--------------------------------------------------------
package example03;

import java.util.ArrayList;

public class ThreadWaitEx02 {
            public static void main(String[] args) throws Exception{
                        Table table = new Table();                        // 여러 쓰레드가 공유하는 객체( 요리사, 손님1, 손님2 )

                        new Thread(new Cook(table), "COOK1").start();
                        new Thread(new Customer(table, "donut"), "CUSTOMER_A").start();
                        new Thread(new Customer(table, "burger"),"CUSTOMER_B").start();

                        Thread.sleep(5000);
                        System.exit(0);
            }
}
class Table{
            String[] dishNames = {"donut", "donut", "burger"};
            static final int MAX_FOOD = 6;

            private ArrayList<String> dishes = new ArrayList<>();

            public synchronized 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;
                                    }
                        }
                        return false;
            }
            public int dishNum() { return dishNames.length; }
}

class Cook implements Runnable{
            private Table table;

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

            @Override
            public void run() {
                        while(true) {
                                    int idx = (int)(Math.random() * table.dishNum());   // 0, 1, 2
                                    table.add(table.dishNames[idx]);

                                    try { Thread.sleep(100); } catch(InterruptedException e) {}
                        }
            }
}

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

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

            boolean eatFood() { return table.remove(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 + " ate a " + food);
                                    }else {
                                                System.out.println(name + " failed to eat.");
                                    }
                        }
            }
}


--------------------------------------------------------
            8-2 ) wait()과 notify()
                        - synchronized로 동기화해서 공유 데이터를 보호하는 것 까지는 좋은데, 

                          특정 쓰레드가 lock을 가지고 오랜 시간을 보내지 않도록 하는게 중요
                        - 만일 계좌에 출금할 돈이 부족해서 한 쓰레드가 lock을 보유한 채로 돈이 입금될때까지 
                          오랜 시간을 보낸다면 다른 쓰레드들은 모두 해당 객체의 lock을 기다리느라 다른 작업들도
                          원활이 진행되지 않을 것
                        - 이런 상황을 개선하기 위해서 고안된 것이 바로 wait()와 notify()
                        - 동기화된 영역의 코드를 수행하다가 작업이 더 이상 진행될 상황이 아니라면
                          wait()을 호출하여 쓰레드가 lock을 반납하고 기다리게 함
                        - 그러면 다른 쓰레드가 lock을 얻어 해당 객체에 대한 작업을 수행할 수 있게 됨
                        - 이는 마치 빵을 사려고 빵집 앞에 줄을 서 있는 것과 유사한데, 자신의 차례가 되었는데도
                          원하는 빵이 나오지 않았다면 다음 사람에게 순서를 양보하고 기다리다가 

                          자신이 원하는 빵이 나오면 통보를 받고 사가는 것
                        - 차이가 있다면, 오래 기다린 쓰레드가 lock을 얻는다는 보장이 없음
                        - wait()이 호출되면 실행중이던 쓰레드는 해당 객체의 대기실(waiting pool)에서 통지를 기다림
                        - notify()가 호출되면 해당 객체의 대기실에 있던 모든 쓰레드들 중에서 임의의 쓰레드만 

                          통지를 받음
                        - notifyAll()은 기다리고 있는 모든 쓰레드들에게 통보를 하지만 그래도 lock을 얻을 수 있는 것은 

                          하나의 쓰레드일 뿐이고 나머지 쓰레드들은 통보를 받긴 했지만, lock을 얻지 못하면 

                          다시 lock을 기다리는 신세가 됨

                                    void wait()
                                    void wait(long timeout)
                                    void wait(long timeout, int nanos)
                                    void notify()
                                    void notifyAll()

                        - wait()은 notify() 혹은 nofityAll()이 호출될때까지 기다리지만, 

                          매개변수가 있는 wait()는 지정된 시간동안만 기다림
                        - 즉, 지정된 시간이 지나면 자동적으로 notify()가 호출되는 것과 같음
                        - waiting pool은 객체마다 존재하는 것이므로 nofityAll()이 호출된다고 해서 

                          모든 객체의 waiting pool에 있는 쓰레드가 깨어나는 것은 아님
                        - notifyAll()은 호출된 객체의 waiting pool에 대기중인  쓰레드만 해당

                        * wait(), notify(), notifyAll()
                                    - Object 클래스에 정의되어 있음
                                    - 동기화블록(synchronized 블록) 내애서만 사용할 수 있음
                                    - 보다 효율적인 동기화를 가능하게 함

                        Object class - toString(), clone(), hashCode(), equals(), wait(), notify(), notifyAll()
                        
-------------------------------------------------------------------

package example04;

import java.util.ArrayList;

public class ThreadWaitEx03 {
            public static void main(String[] args) throws Exception{
                        Table table = new Table();                        // 여러 쓰레드가 공유하는 객체( 요리사, 손님1, 손님2 )

                        new Thread(new Cook(table), "COOK1").start();
                        new Thread(new Customer(table, "donut"), "CUSTOMER_A").start();
                        new Thread(new Customer(table, "burger"),"CUSTOMER_B").start();

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

class Table{
            String[] dishNames = {"donut", "donut", "burger"};
            static 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 ( COOK )");

                                    try {
                                                wait();               // Cook 쓰레드를 기다리게 한다.
                                                Thread.sleep(500);
                                    }catch(InterruptedException e) {}
                        }
                        dishes.add(dish);
                        notify();                    // 기다리고 있는 Customer 쓰레드를 개우기 위함
                        System.out.println("Dishes : " + dishes.toString());
            }
            public void remove(String dishName) {
                        synchronized(this) {
                                    String name = Thread.currentThread().getName();

                                    // 1. 테이블에 음식이 없는 경우
                                    while(dishes.size() == 0) {
                                                System.out.println(name + " is waiting ( CUST )");
                                                try {
                                                            wait();                          // Customer 쓰레드를 기다리게 한다.
                                                            Thread.sleep(500);
                                                } catch(InterruptedException e) {}
                                    }
                                    // 2. 원하는 음식이 없는 경우
                                    while(true) {
                                                for(int i = 0; i < dishes.size(); i++) {
                                                            if(dishName.equals(dishes.get(i))) {
                                                                        dishes.remove(i);
                                                                        notify();             

                                                                        // 기다리고 있는 Cook 쓰레드를 깨우기 위함.
                                                                        return;
                                                            }
                                                }
                                                try {
                                                            System.out.println(name + " is waiting ( CUST )");
                                                            wait();
                                                            Thread.sleep(500);
                                                } catch(InterruptedException e) {}
                                    }
                        }
            }
            public int dishNum() {return dishNames.length; }
}
class Cook implements Runnable{
            private Table table;

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

            @Override
            public void run() {
                        while(true) {
                                    int idx = (int)(Math.random() * table.dishNum());   // 0, 1, 2
                                    table.add(table.dishNames[idx]);

                                    try { Thread.sleep(10); } catch(InterruptedException e) {}
                        }
            }
}
class Customer implements Runnable{
            private Table table;
            private String food;

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

            @Override
            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);
                        }
            }
}


'Java2(주말)' 카테고리의 다른 글

Java2 day15  (0) 2022.05.28
Java2 day14  (0) 2022.05.22
Java2 day13  (0) 2022.05.21
Java2 day12  (0) 2022.05.15
Java2 day11 보충  (0) 2022.05.15