답변:
wait()
및 notify()
방법은 특정 조건이 만족 될 때까지 차단하는 실을 수 있도록하는 메커니즘을 제공하도록 설계된다. 이를 위해 고정 크기의 백업 저장소 요소가있는 블로킹 큐 구현을 작성하려고한다고 가정합니다.
가장 먼저해야 할 일은 메소드가 기다리는 조건을 식별하는 것입니다. 이 경우 put()
상점에 여유 공간이 확보 take()
될 때까지 메소드를 차단하고 리턴 할 요소가있을 때까지 메소드를 차단 하려고합니다 .
public class BlockingQueue<T> {
private Queue<T> queue = new LinkedList<T>();
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public synchronized void put(T element) throws InterruptedException {
while(queue.size() == capacity) {
wait();
}
queue.add(element);
notify(); // notifyAll() for multiple producer/consumer threads
}
public synchronized T take() throws InterruptedException {
while(queue.isEmpty()) {
wait();
}
T item = queue.remove();
notify(); // notifyAll() for multiple producer/consumer threads
return item;
}
}
대기 및 알림 메커니즘을 사용해야하는 방법에 대해 몇 가지 참고할 사항이 있습니다.
첫째, 당신은 보장하기 위해 필요로하는 모든 호출에 wait()
나 notify()
코드의 동기화 된 지역 내합니다 (함께있는 wait()
과 notify()
같은 객체에 동기화되는 호출). 표준 스레드 안전 문제 이외의 이유는 누락 된 신호로 인한 것입니다.
예를 들어 put()
대기열이 가득 차면 스레드가 호출 되어 조건을 확인하고 대기열이 가득 찼음을 확인하지만 다른 스레드가 예약되어 있기 전에 차단할 수 있습니다. 그런 다음이 두 번째 스레드 take()
는 큐의 요소이며 대기 스레드에 큐가 더 이상 채워지지 않았 음을 알립니다. 그러나 첫 번째 스레드는 이미 조건을 확인했기 때문에 wait()
진행이 가능하더라도 일정이 변경 되면 간단히 호출 됩니다.
공유 객체를 동기화 take()
하면 첫 번째 스레드가 실제로 차단 될 때까지 두 번째 스레드의 호출이 진행될 수 없으므로이 문제가 발생하지 않도록 할 수 있습니다.
둘째, 가짜 웨이크 업으로 알려진 문제로 인해 if 문이 아닌 while 루프에서 점검중인 조건을 넣어야합니다. 대기 스레드를 notify()
호출 하지 않고 재 활성화 할 수있는 경우가 있습니다 . 이 검사를 while 루프에 넣으면 가짜 웨이크 업이 발생하면 조건이 다시 검사되고 스레드가 wait()
다시 호출 됩니다.
다른 답변 중 일부에서 언급했듯이 Java 1.5 java.util.concurrent
는 대기 / 알림 메커니즘에 대해 더 높은 수준의 추상화를 제공하도록 설계된 새로운 동시성 라이브러리 ( 패키지)를 도입했습니다 . 이러한 새로운 기능을 사용하면 다음과 같이 원래 예제를 다시 작성할 수 있습니다.
public class BlockingQueue<T> {
private Queue<T> queue = new LinkedList<T>();
private int capacity;
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public void put(T element) throws InterruptedException {
lock.lock();
try {
while(queue.size() == capacity) {
notFull.await();
}
queue.add(element);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while(queue.isEmpty()) {
notEmpty.await();
}
T item = queue.remove();
notFull.signal();
return item;
} finally {
lock.unlock();
}
}
}
물론 실제로 차단 대기열이 필요한 경우 BlockingQueue 인터페이스 의 구현을 사용해야합니다 .
또한 이와 같은 것들 에 대해서는 동시성 관련 문제 및 솔루션에 대해 알아야 할 모든 것을 다루기 때문에 Java Concurrency in Practice를 강력히 권장 합니다.
큐 예제는 아니지만 매우 간단합니다 :)
class MyHouse {
private boolean pizzaArrived = false;
public void eatPizza(){
synchronized(this){
while(!pizzaArrived){
wait();
}
}
System.out.println("yumyum..");
}
public void pizzaGuy(){
synchronized(this){
this.pizzaArrived = true;
notifyAll();
}
}
}
몇 가지 중요한 사항 :
1) 절대로하지 마십시오
if(!pizzaArrived){
wait();
}
항상 while (조건)을 사용하십시오.
while(!pizzaExists){ wait(); }
입니다.2) 대기 / 없음을 호출하기 전에 잠금 (동기화)을 유지해야합니다. 스레드는 깨어나 기 전에 잠금을 획득해야합니다.
3) 동기화 된 블록 내에서 잠금을 얻지 말고 외계인 메서드 (어떻게하는지 잘 모르는 방법)를 호출하지 않도록 노력하십시오. 필요한 경우 교착 상태를 피하기위한 조치를 취하십시오.
4) notify ()에주의하십시오. 무엇을하고 있는지 알 때까지 notifyAll ()을 고수하십시오.
5) 마지막으로 Java Concurrency를 실제로 읽으십시오 !
pizzaArrived
깃발을 사용합니까? 호출하지 않고 플래그를 변경 notify
하면 아무런 영향을 미치지 않습니다. 또한 예제가 작동 wait
하고 notify
호출합니다.
synchronized
키워드, 변수를 선언 중복 volatile
, 그리고 혼란 @mrida을 피하기 위해를 방지하기 위해 권장
비록 당신을 요구 wait()
하고 notify()
특히,이 견적 여전히만큼 중요하다고 생각합니다 :
Josh Bloch, Effective Java 2nd Edition , 항목 69 : 동시성 유틸리티를 선호 wait
하고 notify
(강조) :
사용의 어려움을 감안
wait
하고notify
정확하게, 대신 더 높은 수준의 동시성 유틸리티를 사용한다 [...]를 사용wait
하고notify
이 제공하는 높은 수준의 언어에 비해 직접하는 것은, "동시성 어셈블리 언어"로 프로그래밍 같다java.util.concurrent
. 이유 사용,면 적, 거의 없습니다wait
및notify
새로운 코드 .
notify()
는 AND를 wait()
다시
이 Java Tutorial을 보셨습니까 ?
또한 실제 소프트웨어에서 이런 종류의 물건을 가지고 노는 것을 피하는 것이 좋습니다. 그것을 가지고 노는 것이 좋으므로 그것이 무엇인지 알 수 있지만 동시성은 모든 곳에서 함정을 가지고 있습니다. 다른 사람을위한 소프트웨어를 구축하는 경우 더 높은 수준의 추상화와 동기화 된 컬렉션 또는 JMS 대기열을 사용하는 것이 좋습니다.
그것은 적어도 내가하는 일입니다. 나는 동시성 전문가가 아니므로 가능한 한 손으로 스레드를 처리하는 것을 멀리합니다.
예
public class myThread extends Thread{
@override
public void run(){
while(true){
threadCondWait();// Circle waiting...
//bla bla bla bla
}
}
public synchronized void threadCondWait(){
while(myCondition){
wait();//Comminucate with notify()
}
}
}
public class myAnotherThread extends Thread{
@override
public void run(){
//Bla Bla bla
notify();//Trigger wait() Next Step
}
}
스레딩의 wait () 및 notifyall ()의 예
동기화 된 정적 배열 목록은 리소스로 사용되며 배열 목록이 비어 있으면 wait () 메서드가 호출됩니다. notify () 메소드는 배열 목록에 요소가 추가되면 호출됩니다.
public class PrinterResource extends Thread{
//resource
public static List<String> arrayList = new ArrayList<String>();
public void addElement(String a){
//System.out.println("Add element method "+this.getName());
synchronized (arrayList) {
arrayList.add(a);
arrayList.notifyAll();
}
}
public void removeElement(){
//System.out.println("Remove element method "+this.getName());
synchronized (arrayList) {
if(arrayList.size() == 0){
try {
arrayList.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
arrayList.remove(0);
}
}
}
public void run(){
System.out.println("Thread name -- "+this.getName());
if(!this.getName().equalsIgnoreCase("p4")){
this.removeElement();
}
this.addElement("threads");
}
public static void main(String[] args) {
PrinterResource p1 = new PrinterResource();
p1.setName("p1");
p1.start();
PrinterResource p2 = new PrinterResource();
p2.setName("p2");
p2.start();
PrinterResource p3 = new PrinterResource();
p3.setName("p3");
p3.start();
PrinterResource p4 = new PrinterResource();
p4.setName("p4");
p4.start();
try{
p1.join();
p2.join();
p3.join();
p4.join();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Final size of arraylist "+arrayList.size());
}
}
if(arrayList.size() == 0)
하십시오. 여기서 실수 일 것 같습니다.
notify
하나의 스레드 만 깨 웁니다. 두 개의 소비자 스레드가 요소를 제거하기 위해 경쟁하는 경우 하나의 알림이 다른 소비자 스레드를 깨울 수 있습니다.이 알림은 그것에 대해 아무 것도 할 수 없으며 다시 생산자 대신 새 요소를 삽입하기를 원했던 잠자기 상태로 돌아갑니다. 생산자 스레드가 깨어나지 않고 아무 것도 삽입되지 않으며 이제 세 스레드가 모두 무한정 잠자 게됩니다. 가짜 웨이크 업이 문제의 원인이라고 잘못 말한대로 이전 의견을 삭제했습니다. (