on
Java Ch 13 쓰레드
Java Ch 13 쓰레드
● ch 13-1~2 프로세스와 쓰레드 멀티쓰레드의 장단점
· 프로세스와 쓰레드
- 프로세스: 실행중인 프로그램, 자원(resource, 자원이란: 메모리,cpu 등) 과 쓰레드로 구성
- 쓰레드: 프로세스 내에서 실제 작업을 수행. 모든 프로세스는 최소한 하나의 쓰레드를 가지고 있다.
# 프로세스: 쓰레드= 공장(일꾼이 일하는 환경):일꾼
// 멀티 쓰레드를 쓰면 여러 작업을 동시에 나눠서 할 수 있고, 작업을 보다 효율적으로 처리할 수 있다. 보통 프로그램들은 다 멀티쓰레드로 작성되어있다.
// 쓰레드 개수 확인: 작업관리자에서 세부정보로 간다. PID는 프로세스 ID, 상태는 프로세스 실행 여부, 사용자이름에 우클릭하면 열선택-쓰레드 확인
· 멀티쓰레드의 장단점
## 하나의 새로운 프로세스를 생성하는 것보다 하나의 새로운 쓰레드를 생성하는 것이 더 적은 비용이 든다.
ex) 2 프로세스 1 쓰레드(2공장 일꾼 한 명씩) vs 1 프로세스 2 쓰레드(1공장 일꾼 2명)
- 장점
1. 시스템 자원을 보다 효율적으로 사용할 수 있다. (하나의 프로세스로)
2. 사용자에 대한 응답성(responseness)이 향상된다. // 여러 프로세스를 거치면 느려진다.
3. 작업이 분리되어 코드가 간결해진다.
- 단점
1. 동기화(synchronization) 에 주의해야한다. (같은 공장에서 여러 쓰레드를 공유하다보니 발생)
2. 교착상태(dead-lock) 가 발생하지 않도록 주의해야한다. (서로에게 필요한 자원을 각자 갖고있어서 대척하는 경우)
3. 각 쓰레드가 효율적으로 고르게 실행될 수 있게 해야 한다. (특정 쓰레드가 작업할 기회를 못 가질 수도 있어서 고르게 작동하도록 짜야한다)
## 프로그래밍 할 때 고려해야 할 사항들이 많음. => 멀티쓰레드 짜는 게 어렵지않으나, 효율적으로 잘 작동하게 하는 것이 중요! ===> 이처럼 코드도 처음에는 많이 코딩하고 그 이후에는 에러x, 효율적인 코드 짜는 것에 집중해야한다.
● ch 13-3~6
· 쓰레드의 구현과 실행 // 2가지 방법이 있다.
// 둘 다 작업할 내용을 run() { --- } 안에 넣어주면 된다. 구현부 {}을 넣은 것처럼
1. Thread 클래스를 상속 //단일 상속 ㅠ
2. Runnable 인터페이스를 구현
//자바는 단일상속이므로 쓰레드를 상속받으면 다른걸 상속 못 받으니 인터페이스로 구현하는 것이 낫다. (유연함)
// MyThread2를 통해 외부에서 run(){}을 제공(구현)해서 Thread()에 매개변수로 넣어준다.
· 예제 13_1
// 각 방법마다 get 메서드를 호출하는 방법이 다르다. 상속은 this, 구현은 currentThread()로
// 싱글쓰레드는 하나의 메서드가 끝나야 다음 메서드 but 멀티쓰레드는 동시에 작동해서 이와 같이 섞인다.
· 쓰레드의 실행- start()
- 먼저 쓰레드를 생성한 후에 start()를 호출해야 쓰레드가 작업을 시작한다.
// 쓰레드를 실행하면 즉시 실행되는 게 아니라 실행가능 상태가 되는거다.
// 그리고 OS 스케줄러가 실행 순서를 결정한다. 스레드는 os스케쥴러에게 의존적인 성격을 가진다. 자바가 독립적인 플랫폼이라고는 하나 os에 종속적인 몇 개가 있는데, 그 중 하나가 쓰레드
· start()와 run()
- 쓰레드를 실행할 때, start()를 호출해야하는 이유
=> run()을 직접 호출하면 하나의 스택에 하나의 쓰레드에서 실행되는 것이다. start()을 호출해서 새로운 호출스택을 생성하고 거기에 run() 작업, 이후 start()는 역할이 끝나는 것
● ch 13- 7~13 싱글 쓰레드와 멀티 쓰레드, 쓰레드의 I/O 블락킹
· main 쓰레드
- main 메서드의 코드를 수행하는 쓰레드 // 그냥 원래 쓰던 main 메서드를 실행하는 것
- 쓰레드는 사용자 쓰레드(main 메서드)와 데몬 쓰레드가 있다.
// 실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다. 즉, 멀티쓰레드면 메인이 종료되도 프로그램ing
// 데몬 쓰레드는 보조 쓰레드인데 사용자 쓰레드가 하는걸 보조해주는 역할을 한다.
public static void main(String args[]) { ThreadEx11_1 th1 = new ThreadEx11_1(); ThreadEx11_1 th2 = new ThreadEx11_2(); th1.start(); th2.start(); startTime = System.currentTimeMills(); try{ th1.join(); // main 쓰레드가 th1의 작업이 끝날 때까지 기다린다. th2.join(); // main 쓰레드가 th2의 작업이 끝날 때까지 기다린다. } catch(InterruptedException e) {} sop("소요시간:“ + (System.currentTimeMillis() } } // try-catch문을 없애면 th1, th2 작업보다 main 쓰레드가 먼저 끝나버려서 소요시간:0으로 나온다. // 유지하면 th1, th2 작업이 끝나고 main 쓰레드가 마무리해서 소요시간이 나온다.
· 싱글쓰레드와 멀티 쓰레드
아래 그림은 싱글스레드일 떄와 멀티쓰레드일 때를 나타낸 것. 멀티 쓰레드를 보면 A 작업을 햇다 B를 햇다를 반복하는데, 이것을 contextSwitching(문맥교환) 이라고 한다. 실행할 정보를 바꾸기 때문에 여기서 시간이 소요된다. 그래서 동시에 실행은 되지만 하나의 작업을 쭉 실행하는 것보다는 조금 더 오래 걸린다.
## 시간이 걸림에도 동시에할 수 있다는 장점
// th1와 th2의 작업 순서와 시간은 OS스케줄러가 알아서 판단 하기에(OS 알고리즘) 우리가 알 수 없다.
· 쓰레드의 I/O 블락킹(blocking)
- I/O 란 input/output, 입력/출력을 줄여서 쓴 것.
- 블락킹이란 막히는 것을 의미, 입출력 시 작업이 중단되는 것을 블락킹이라고 한다
// 언제 i/o 블락킹이 일어나느냐?
=> 싱글쓰레드에서 사용자입력과 카운트다운 메서드가 있는데 사용자 입력 구간에서 아무것도 하지않으면
계속 기다려야 하는상황이 발생한다. => but 멀티쓰레드를 하면 기다릴 필요가 없어진다. (외부 요인이 발생해도)
// 이를 가지고 있는 자원을 효율적으로 쓴다고 말한다.
● ch 13-14~17 쓰레드의 우선순위, 쓰레드 그룹
· 쓰레드의 우선순위 (priority of thread)
- 작업의 중요도 에 따라 쓰레드의 우선순위를 다르게 하여 특정 쓰레드가 더 많은 작업시간을 갖게 할 수 있다.
자바에서는 쓰레드의 우선순위를 1부터 10 까지 보유 가능. 1부터 10까지의 값은 JVM 에서 정해져있는 값이고
윈도우 OS 같은경우는 32단계로 나누어져있다. 우선순위는 프로그램이 실행된 이후에도 변경할 수 있다.
// 우선순위 지정 안 해주면 보통 우선순위가 된다. 지정해주고 싶으면 setPriority()
// 우선순위가 같으면 동일, but 우선순위가 높으면 일찍 끝난다. but 이론적으로 이렇게 돌아가야하는데,,, 그저 희망사항을 담아서 OS 스케줄러에 부탁하는 것 이다. OS 스케줄러는 공평하게 잘 돌아가게 하는 것이 목적이기에 참고만 할 뿐이다. Ex 13_6을 실행하면 우선순위가 높은게 늦게 끝나기도.... 물론 그럴 확률이 줄어드는 것뿐
// 작업관리자에서 세부정보 들어가서 프로그램 우클릭- 우선순위설정 가능(실시간이 제일 최우선 but 컴퓨터에 무리)
· 쓰레드 그룹
- 서로 관련된 쓰레드를 그룹으로 묶어서 다루기 위한것
- 모든 쓰레드는 반드시 하나의 쓰레드 그룹에 포함되어 있어야 한다.
- 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 main 쓰레드 그룹에 속한다
- 자신을 생성한 쓰레드(부모쓰레드) 의 그룹과 우선순위를 상속받는다. //이로 인해 Main 쓰레드에 자동으로 속함.
- getThreadGroup() 메서드는 Thread 클래스 안에 내장되어있고, 쓰레드 자신이 속한 쓰레드 그룹 반환
- uncaughtException 메소드는 ThreadGroup 클래스 안에 있는 메섣. 예외를 처리하지못하면 JVM이 처리한다고 알고 있는데 그걸 우리가 다른동작을 하도록 변경 가능. printstacktrace 출력 -> 다른 동작
// ThreadGroup 에 있는 스레드가 예외처리를 못해서 죽었을 때 수행될 동작을 우리가 오버라이딩 해줄 수 있다.
// 일단은 이런 게 있다는 정도만
· 쓰레드 그룹의 메서드
// 일단은, 스레드 그룹 안에 또 다른 스레드를 줄 수 있고, 스레드 그룹 안에 일괄적으로 내릴 수 있는 명령들이 있다. 그리고 스레드는 기본적으로 그룹으로 묶여서 다루어진다는 것만 알고 넘어가라.
● ch 13-18~21 데몬 쓰레드, 쓰레드의 상태
// 쓰레드의 종류는 일반쓰레드와 데몬쓰레드 두가지가 있다. // 쓰레드가 생성되는 시점은 start()를 실행한 후 부터
· 데몬쓰레드 (daemon thread)
- 일반 쓰레드(non-daemon thread) 의 작업을 돕는 보조적인 역할을 수행 //데몬 쓰레드가 아니면 일반 쓰레드
- 일반 쓰레드가 모두 종료되면 자동적으로 종료된다.
- 가비지 컬렉터, 자동저장, 화면 자동갱신 등에 사용된다.
- 데몬 쓰레드는 작성법: 무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.
// 무한루프여도 일반스레드가 종료되면 같이 종료된다.
// 무작정 계속 실행하면 좀 그러니까 중간에 쉬는시간을 부여=> sleep() 메소드를 쓴다.
// start() 실행되면 이후 스레드를 일반 혹은 데몬으로 바꿀 수가 없다.
// setDemon 이 반드시 start 위에 올라와있어야한다.
// 오류 조심: 데몬스레드가 아니면 일반스레드가 끝났음에도 불구하고 계속 실행된다... 그래서 데몬스레드 설정해주는 게 반드시중요하다(무조건 실행 전 작성)
// 결과: 처음에는 autosave가 false여서 3초마다 호출해도 안 되다가 5초 때부터 autoSave 가 true가 됬기에 그 다음부터 3초마다 자동저장되는 것! (데몬인지와 상관없이 쓰레드끼리는 공유된다)
====> 의문: 3초마다 호출인데, 데몬쓰레드가 일반쓰레드보다 앞선건가??? 이건 직접해보면서 알아내야겠다.
· 쓰레드의 상태(5가지 상태)
- 과정
1. 처음에 쓰레드를 생성만한 상태인데, start() 를 하게되면 RUNNABLE(실행대기)상태가 된다. 먼저 온 스레드가 먼저 실행되기 때문에 줄을 서야 한다. 자기한테 주어진 시간이 다 되었으면 다시 뒤로 가서 줄을 선다. 이걸 계속 반복.
2. 내가 해야될 작업이 끝나거나 stop() 이라는 메소드가 호출되면 스레드가 소멸된다. 그 상태는 TERMINATED이다.
3. 쓰레드가 작업 도중에 일시정지해야할 때 아래 메서드들을 통해 일시정지(쉼터)공간으로 가게 된다
- suspend() 는 일시정지 =>Waiting에 해당
- sleep() 잠자기 =>Waiting에 해당
- wait() 기다리기
- join() 다른스레드 기다리기
- i/o block 입출력 대기 => Blocked에 해당
- time-out 은 시간종료 ex) sleep(5) 5초라는 시간이 지나면 시간종료를 하고 다시 줄을 선다.
- interrupt() 는 잠자는 메서드를 급하게 깨우는 것이다. 시간이 안 됬는 데도
- resume() 은 일시정지인 suspend() 와 반대이다. 일시정지를 풀고 재개하는 것
- notify() 은 wait() 하고 한쌍으로서 푸는 메서드
· 쓰레드의 실행제어
- 쓰레드의 실행을 제어할 수 있는 메서드가 제공된다.
// 쓰레드를 실행하면 종료될 때까지 놔뒀는데, 이걸 제어하는 것!
// 이 메서드들이 되게 중요하고 적절히 사용하고 활용해서 보다 효율적인 프로그램의 작성을 해야한다.
// static이 붙은 sleep() 과 yield()(자기 시간을 다른 쓰레드에게 양보) 는 자기 자신에게만 호출하는 것
// 다른 쓰레드에 적용
// interrupt() 는 sleep이나 join을 깨우는 것임
● Ch 13-22~25 sleep() interrupt()
· sleep() // static 메서드이다. 현재 스레드(자기 자신)에 대해서 동작한다.
- 현재 쓰레드를 지정된 시간동안 멈추게 한다.
- 예외처리를 해야한다. (interruptedException 이 발생하면 깨어남) //예외를 발생시킬 수 있는 메서드이기에
// 깨어나는 것은 time up되거나 interrupted 로 깨는 것이다.
// 누군가 깨우면 throw IntrruptedException 발생시켜 깨기위한 것이니 try-catch문을 쓴 것이고 {}을 비워둔 것!!
// 특정 스레드를 지정해서 멈추게 하는 것이 불가하기에, Thread.로 적어야한다! 참조변수로 적지말라.
=> 항상 이렇게 예외처리하는 것은 불편하므로 메서드를 하나 만들어두어도된다.
// try-catch 대신에 이걸 작성하고, delay(15); 로만 간단히 쓸 수 있다.
· interrupt() 메소드
- 대기상태(wating = sleep, wait, join 등으로 작업 중단)인 스레드를 실행대기 상태(RUNNABLE)로 만든다.
// interrupt 상태: 처음에 시작할 떈 값이 false 이다. 실행을 안했으니까 실행을 하고나서는 값이 true로 바뀐다.
// boolean isInterrupted()를 쓰면 timeup 돼서 깬 것인지, 아니면 누가 깨워서 일어난 건지 알 수 있다. T/F 반환
// static boolean interrupted는 상태를 알려주고 false로 초기화까지 하는 것이다.
// interrupt 당하는 쓰레드 참조변수.isInterrupted() vs 실행하는 쓰레드.interrupted() =>참조변수의 차이!!!
ex) 다운로드하다가 취소버튼 -> !isInterrupted() 같은 걸 호출해서 멈추는 것이다. (thread하다가 도중에 중단시키고 싶을 때 쓰는 것이 interrupted) 혹은 카운트하다가 버튼을 누르면 카운트가 멈추는...
● ch 13-26,27 suspend() resume()
· suspend() 쓰레드 일시정지, resume() 일시정지->재개, stop() 완전정지(쓰레드 즉시 종료)
- 쓰레드의 실행을 일시정지, 재개, 완전정지 시킨다. but 이 메서드들은 deprecated 되었다.
=> dead-lock 교착상태 일으킬 가능성
# 교착상태란: 서로 두 쓰레드가 서로한테 필요한걸 가지고 있어서 진행이 안 되는 상태
deprecated가 안뜨게하려면 직접 메서드로 구현하면된다.
=> 처음에 suspended 와 stopped의 속성 값을 초기화하고 뒤에 public void 메서드들을 작성해서 호출하는 것이다.
// 두 개의 속성 값 변수를 이용해서 메서드들이 변수값을 바꿔서 run()에 있는 if문과 while문을 제어하는 것이다
// Thread th; // 생성자 선언
// MyThread(String name){
th = new Thread(this, name); // Thread(Runnable r, String name) 이다.
} ## 이 부분을 잘 주의해서 볼 것!
# Volatile: 변수가 쉽게 변한다는 뜻이다. 자주 변하니까 복사본 쓰지말고 원본 가지고와서 읽어온나.
쓰레드가 종료되었음에도 terminated가 종료되지않으면 변수 앞에 valatile 을 붙이면된다. 이유는 cpu의 코어(두뇌) 들이 RAM에 저장되어있는 변수값을 복사해서 Cashe 에 저장하는 데 복사본을 쓰면 값이 계속 바뀌면 계속 복사해야하는데 잘 안 먹히는 경우가 많아서... volatile을 붙이면 복사본을 사용 못 하게한다. 원본을 직접 가서 읽어온다.
그래서 값이 바뀐 걸 직접 알 수 있다.
● ch 13-28,29 join(), yield()
· join()
- 지정된 시간동안 특정 쓰레드가 작업하는 것을 기다린다.
==> 쓰는방식은 sleep() 과 비슷하다! ry~catch 로 감싸야하고 예외처리가 필수이다.
// 인터럽트가 발생하면 기다리는걸 끝내고 다음으로간다.
· 예제
-> 메모리가 부족한 경우, gc() 가비지컬렉션을 실행하려고 한다. 잠자고 있던 쓰레드 gc를 깨우고나서 바로 메모리사용하는 것이 아니라 join()을 통해 gc()가 작업할 시간을 주고나서 메모리사용하도록 한다.
· yield() // static 메서드이다.
- 남은 시간을 다음 쓰레드에게 양보하고, 자신(현재 쓰레드)은 실행대기 한다.
// 내가 만약 5초를 부여받았는데 3초 만에 끝낼수있으면 2초가 남는데 뒤에 양보하고 자기는 다시 뒤로가서 줄서기
- yield() 와 interrupt()를 적절히 사용하면, 응답성과 효율을 높일 수 있다.
ex)
// while 이 true 이고 if 가 false 이면 일시정지상태이면 작업수행을 해야하는데 못한다..
=> while 문은 게속 돌고있는데 현재 작업이 일시정지이므로 뒤에 메서드들은 계속 기다려야한다/ (busy-waiting)
// else를 if문에 넣는다. if문이 막힌 상태면 else문을 작동해서 양보하라는 뜻.
- suspend() 와 stop() 에는 interrupt() 메소드를 넣어야한다. 바뀐 다음 스레드가 자고 있을 수 있으니, suspend() 나 stop()이 발동되면 바로 인터럽트를 발생시켜 다음으로 넘어갈 수 있게 하기 위함. 자고 있으면 1초 후에 stop..
=> stop을 눌렀는데 바로 안 먹히면 응답성이 좋은 프로그램이 아니다. yield 는 os 스케줄러한테 통보하는것이다.
=> 반드시 yield가 동작한다는 법은 없다. 이것도 OS스케줄러 맘이다.
● ch 13-30~33 쓰레드의 동기화
· 쓰레드의 동기화(synchronization)
- 멀티 쓰레드 프로세스에서는 다른 쓰레드의 작업에 영향을 미칠 수 있다.
// 여러 쓰레드가 같은 자원을 공유하기 때문에 메모리도 공유한다. 한 쓰레드가 작업하던걸 마치지 못하고 다른 쓰레드로 넘어가게될 경우 그 쓰레드가 다른 쓰레드의 작업에 영향을 줄수있다.
- 진행중인 작업이 다른 쓰레드에게 간섭받지않게 하려면 동기화가 필요
# 쓰레드의 동기화: 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는 것
- 동기화를 하려면 간섭받지 않아야 하는 문장들을 ‘임계 영역’으로 설정 //못 들어오게 여러 문장을 하나로 묶는다.
// A,B,C,D,E 라는 작업구역이 있는데 내가 C를 작업하다가 중간에 나가면 다른쓰레드가 C로 들어오지못한다
- 임계영역은 락(lock)을 얻은 단 하나의 쓰레드만 출입가능(객체1개에 락1개)
· synchronized 를 이용한 동기화
- synchronized 로 임계영역(lock 이걸리는 영역)을 설정하는 방법 2가지
1. 메서드 전체를 임계영역 지정
- 반환타입 앞에다가 synchronized를 붙이면 이 메소드에 있는 구현부가 임계영역으로 바뀐다.
but 임계영역은 한번에 한 쓰레드만 사용할수있기 때문에 영역을 최소화 해야한다. 임계영역이 많을수록 성능이 떨어진다. => 멀티쓰레드의 장점이 여러 개가 동시에 돌아가는것인데 이것에 방해되는 행동이므로 임계영역은 가능한 최소한의 갯수와 최소한의 코드로 작성하자.. 가급적 사용x
2. 특정한 영역을 임계 영역으로 지정
// this는 이 객체를 말한다.
· 예제
- private으로 해야 동기화가 의미가 있다.
- A와 B 쓰레드가 있는데, OS스케줄러가 맘대로 순서를 결정해주다보니, 잔고가 200원밖에 없는데, A가 200원을 꺼내려고해서 if문을 통과하고 돈을 꺼내기전에 B가 들어와서 if문 통과하고 잔고가 가져가면, 다시 A로 돌아갈 때 –잔고가 나오는 문제가.... 그래서 임계영역을 설정하고, 혼자 키를 갖고 본인이 다 할 때까지 잠궜다가 임계영역 벗어나면 키를 반납하고 쓰레드 B가 들어올 수 있는 것!
● ch 13-34~36 wait() 과 notify()
· wait() 와 notify()
- 효율을 높이기 위해 wait() 과 notify() 이다. wait은 기다리기, notify는 통보, 알려주기
// 동기화를 하면 데이터가 보호는 되는데 비효율적이다. (한 쓰레드만 임계영역에 들어가기 때문에)
- Object클래스에 정의되어 있으며, 동기화 블록 내에서만 사용할수있다.
wait() - 객체의 lock을 풀고 쓰레드를 해당 객체의 wating pool 에 넣는다
notify()- wating pool에서 대기중인 쓰레드 중의 하나를 깨운다
notifyAll() - wating pool 에서 대기중인 모든 쓰레드를 깨운다 // 공평하게 다 깨우고 해당되는 애만 작동하니까
// 출금할 돈이 없으면? 기다려야겠지.
// 위 코드에서 문제점은 withdraw에서 동기화가 되어있기 때문에 다른 메서드가 deposit 메서드에 접근 불가능한 상태이다. 그때 방법이 wait() 을 호출하는 것이다.
// Account 객체에 wating pool 이 있는데 여기서 출금쓰레드 A가 키를 반납하고 풀고 기다리는 것이다.
그러면 입금하려는 쓰레드 B가 락을 가질수있는 것이다. (한 객체에는 하나의 락만 걸 수 있기 때문이다.)
// 입금을 다 마치고 출금쓰레드에게 notify() 한다 (알려주는기능)
// 그럼 출금쓰레드는 아까 wait 햇던곳으로 다시 돌아가서 다시 작업을 시작한다.
// 또 부족하면 여태까지 했던 과정을 계속 반복한다. => 이게 바로 wait & notify 방식이다.
· wait() 와 nofiy() 예제1 ==> 오우, 좀 어려운데???
- 요리사는 Table에 음식을 추가, 손님은 Table의 음식을 소비
- 요리사와 손님이 같은 객체(Table)을 공유하므로 동기화가 필요
테이블이라는 객체가 있고 음식들을 모아놓을 수 있는 ArrayList 객체를 생성했다.
// 동기화가 안 되어있다면,
- 요리사가 Table에 요리를 추가하는 과정에서 손님이 요리를 먹음.
// ConcuurentModificationException(읽기 수행 중에 add나 remove 한 경우)
- 하나 남은 요리를 손님2가 먹으려는데 손님1이 먹음
// IndexOutOfBoundsException(손님 1이 먹어서 0이 된 상태에서 손님 2가 먹으니 –1이 되어서)
// 동기화(dishes를 다루는 메서드에 동기화!)
- 각 메서드 이름앞에 동기화를 써준다. 근데 동기화를 쓰면 또 문제가 발생한다.
- 예외는 발생하지 않지만, 손님이 음식이 없으면 table에 lock 건 상태를 지속한다. 그러면 요리사가 table의 lock을 얻을 수 없어서 음식을 추가하지 못한다. => 이럴 때 wait(), nofify를 쓴다
- 테이블 객체를 생성하고 요리사 1명과 손님 2명을 객체로 하는 쓰레드 총 3개를 구현
=> 음식이 없으면 손님을 기다리게 하는 wait, 음식을 먹고나서 요리사를 호출하면 notify, 원하는 음식이 없으면 wait
// add 메서드 – 음식을 추가했으면 기다리고 있는 손님에게 notify – 요리사는 테이블 가득차면 wait
// remove 메서드 – 음식이 하나 사라지면, 요리사를 깨워서 혹시나 부족하면 음식 채우게끔
==> 이후, 전과 달리 한 쓰레드가 키를 오래 쥐는 일이 없어짐. 효율적이 됨. but wait(),notify() 대상이 불분명 => 이렇게 구분 안되는 단점을 해결하기위한 것은 Lock & Condition 이다.
●
·
·
·
·
·
·
·
from http://ing-til-death.tistory.com/68 by ccl(A) rewrite - 2021-10-08 22:26:58