Programming

자바 : notify () 대 notifyAll () 다시

procodes 2020. 2. 28. 19:39
반응형

자바 : notify () 대 notifyAll () 다시


만약 "의 차이에 대해 하나의 구글 notify()notifyAll()"다음 설명의 많은 (떨어져 javadoc의 단락을 떠나) 나타납니다. 그것은 모두 최대 깨울되는 대기하는 스레드의 수를 요약된다 : 하나 notify()와 모두를 notifyAll().

그러나 (이러한 방법의 차이점을 올바르게 이해하면) 모니터를 추가로 확보하기 위해 항상 하나의 스레드 만 선택됩니다. 첫 번째 경우에는 VM에 의해 선택된 것, 두 번째 경우에는 시스템 스레드 스케줄러에 의해 선택된 것. 두 가지 (일반적인 경우)에 대한 정확한 선택 절차는 프로그래머에게 알려지지 않았습니다.

무엇 유용 차이는 통지 ()의 notifyAll () 다음? 뭔가 빠졌습니까?


그러나 (이러한 방법의 차이점을 올바르게 이해하면) 추가 모니터 수집을 위해 항상 하나의 스레드 만 선택됩니다.

맞지 않습니다. 호출 에서 차단 된 모든 스레드를 o.notifyAll()깨 웁니다 . 스레드는 하나씩 만 반환 있지만 각각 차례가됩니다.o.wait()o.wait()


간단히 말해 스레드가 알림을 기다리는 이유에 따라 다릅니다. 대기중인 스레드 중 하나에 무언가 발생했음을 알리고 싶거나 동시에 모든 스레드에 알리고 싶습니까?

경우에 따라 대기가 완료되면 모든 대기 스레드가 유용한 조치를 취할 수 있습니다. 예를 들어 특정 작업이 완료되기를 기다리는 일련의 스레드가 있습니다. 작업이 완료되면 모든 대기 스레드가 비즈니스를 계속할 수 있습니다. 이 경우 notifyAll ()사용 하여 대기중인 모든 스레드를 동시에 깨울 수 있습니다.

다른 경우, 예를 들어 상호 배타적 잠금의 경우 대기 스레드 중 하나만 알림을받은 후 유용한 작업을 수행 할 수 있습니다 (이 경우 잠금 획득). 이 경우 notify ()를 사용하는 것이 좋습니다 . 올바르게 구현하면 이 상황에서도 notifyAll ()사용할 있지만 어쨌든 아무것도 할 수없는 스레드를 불필요하게 깨울 것입니다.


대부분의 경우 조건을 기다리는 코드는 루프로 작성됩니다.

synchronized(o) {
    while (! IsConditionTrue()) {
        o.wait();
    }
    DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}

이렇게하면 o.notifyAll()호출이 둘 이상의 대기 스레드를 깨우고 make에서 돌아온 첫 번째 스레드 o.wait()가 조건을 false 상태로 유지하면 깨어 난 다른 스레드가 대기 상태로 돌아갑니다.


분명히, notify대기 세트에서 하나의 스레드를 notifyAll깨우고 대기 세트의 모든 스레드를 깨 웁니다. 다음 논의는 의심을 없애야합니다. notifyAll대부분의 시간 동안 사용해야합니다. 어떤 것을 사용해야할지 확실하지 notifyAll않으면를 사용 하십시오. 다음 설명을 참조하십시오.

매우주의 깊게 읽고 이해하십시오. 궁금한 점이 있으면 이메일을 보내 주시기 바랍니다.

생산자 / 소비자를보십시오 (가정은 두 가지 방법이있는 ProducerConsumer 클래스입니다). 그것은 (사용하기 때문에) 고장입니다 notify-그렇습니다-대부분의 경우에도 작동 할 수도 있지만 교착 상태를 유발할 수도 있습니다. 이유를 알 수 있습니다.

public synchronized void put(Object o) {
    while (buf.size()==MAX_SIZE) {
        wait(); // called if the buffer is full (try/catch removed for brevity)
    }
    buf.add(o);
    notify(); // called in case there are any getters or putters waiting
}

public synchronized Object get() {
    // Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
    while (buf.size()==0) {
        wait(); // called if the buffer is empty (try/catch removed for brevity)
        // X: this is where C1 tries to re-acquire the lock (see below)
    }
    Object o = buf.remove(0);
    notify(); // called if there are any getters or putters waiting
    return o;
}

먼저

대기를 둘러싼 while 루프가 필요한 이유는 무엇입니까?

while이 상황이 발생 하면 루프 가 필요합니다 .

소비자 1 (C1)은 동기화 된 블록에 들어가고 버퍼가 비어 있으므로 C1은 ( wait통화 를 통해) 대기 세트에 놓입니다 . 소비자 2 (C2)는 동기화 된 메서드 (위의 Y 지점에서)에 진입하려고하지만 생산자 P1은 객체를 버퍼에 넣은 다음을 호출합니다 notify. 대기중인 유일한 스레드는 C1이므로 깨우고 이제 X 지점 (위)에서 오브젝트 잠금을 다시 확보하려고 시도합니다.

이제 C1과 C2가 동기화 잠금을 획득하려고합니다. 그들 중 하나 (결정적이지 않게)가 선택되어 메소드에 들어가고, 다른 하나는 차단됩니다 (기다리는 것이 아니라 차단되어 메소드의 잠금을 얻으려고 시도합니다). C2가 먼저 잠금을 얻는다고 가정 해 봅시다. C1이 여전히 차단 중입니다 (X에서 잠금을 획득하려고 시도 함). C2가 메소드를 완료하고 잠금을 해제합니다. 이제 C1이 잠금을 획득합니다. whileC1이 루프 검사 (guard)를 수행하고 존재하지 않는 요소를 버퍼에서 제거 할 수 없기 때문에 루프 가있는 것이 무엇인지 추측 하십시오 (C2는 이미 그것을 얻었습니다!). 우리는이하지 않은 경우 while, 우리는을 얻을 것 IndexArrayOutOfBoundsExceptionC1 버퍼에서 첫 번째 요소를 제거하려고로!

지금,

자, 이제 우리는 notifyAll이 필요한 이유는 무엇입니까?

위의 생산자 / 소비자 예에서와 같이 벗어날 수있는 것처럼 보입니다 notify. 생산자와 소비자를위한 대기 루프 의 경비원 이 상호 배타적 임을 증명할 수 있기 때문에 이런 식으로 보입니다 . 즉, put메소드뿐만 아니라 메소드 에서 스레드를 대기 할 수없는 것처럼 보입니다 get. 왜냐하면 사실이기 위해서는 다음이 사실이어야하기 때문입니다.

buf.size() == 0 AND buf.size() == MAX_SIZE (MAX_SIZE가 0이 아니라고 가정)

그러나 이것은 충분하지 않습니다 notifyAll. 우리는 사용해야 합니다. 왜 그런지 보자 ...

크기가 1 인 버퍼가 있다고 가정합니다 (예를 쉽게 따르기 위해). 다음 단계로 인해 교착 상태가 발생합니다. 통지로 스레드가 깨어 나면 언제든지 JVM에 의해 결정적으로 선택되지 않을 수 있습니다. 즉, 대기 스레드가 깨어날 수 있습니다. 또한 메소드에 진입 할 때 여러 스레드가 차단되는 경우 (즉, 잠금을 확보하려는 경우) 획득 순서는 비 결정적 일 수 있습니다. 또한 스레드는 한 번에 하나의 메소드에만있을 수 있음을 기억하십시오. 동기화 된 메소드는 하나의 스레드 만 클래스에서 (동기화 된) 메소드를 실행 (즉, 잠금 보유) 할 수 있도록합니다. 다음과 같은 일련의 이벤트가 발생하면 교착 상태 결과가 발생합니다.

1 단계 :
-P1은 1 문자를 버퍼에 넣습니다.

2 단계 :
-P2 시도 put-대기 루프 확인-이미 문자-대기

3 단계 :
-P3 시도 put-대기 루프 확인-이미 문자-대기

STEP 4 :
- C1 시도 1 개 문자 얻을
받는 항목에 블록 - 1 개 문자를 얻을 C2 시도 - get방법
받는 항목에 블록 - C3 시도는 1 문자를 얻을 - get방법

STEP 5 :
- C1의 실행 get방법 - 숯, 호출 도착 notify, 이탈 방법
더 - notifyP2 깨어나는
-하지만, C2의 입장에서 P2 블록 있도록 (P2 잠금을 재 획득한다) 수 P2 전에있어서 진입 put방법
- C2 대기 루프를 검사하고 버퍼에 더 이상 문자가 없으므로 대기합니다
. C3은 C2 이후에 메소드에 들어가지만 P2 전에 대기 루프를 검사하고 버퍼에 더 이상 문자가 없으므로 대기합니다.

6 단계 :
-지금 : P3, C2 및 C3이 대기 중입니다!
-마지막으로 P2는 잠금을 획득하고 문자를 버퍼에 넣고 알림을 호출하고 메소드를 종료합니다.

7 단계 :
-P2의 알림이 P3을 깨 웁니다 (스레드가 깨어날 수 있음을 기억하십시오)
-P3는 대기 루프 조건을 확인합니다. 버퍼에 이미 문자가 있으므로 대기합니다.
-더 이상 알림을 호출 할 스레드가없고 3 개의 스레드가 영구적으로 일시 중단되었습니다!

해결책 : 교체 notifynotifyAll생산자 / 소비자 코드 (위)이다.


유용한 차이점 :

  • 대기중인 모든 스레드가 상호 교환 가능하거나 (깨우는 순서는 중요하지 않음) 대기 스레드가 하나만있는 경우 notify ()를 사용하십시오 . 일반적인 예는 대기열에서 작업을 실행하는 데 사용되는 스레드 풀입니다. 작업이 추가 될 때 스레드 중 하나에 깨우고 다음 작업을 실행하고 다시 절전 모드로 돌아가도록 통지됩니다.

  • 대기중인 스레드의 목적이 다르고 동시에 실행될 수있는 다른 경우에는 notifyAll ()사용하십시오 . 예를 들어 공유 리소스에 대한 유지 관리 작업이 있습니다. 여기서 리소스에 액세스하기 전에 여러 스레드가 작업이 완료되기를 기다리고 있습니다.


자원이 어떻게 생산되고 소비되는지에 달려 있다고 생각합니다. 한 번에 5 개의 작업 개체를 사용할 수 있고 5 개의 소비자 개체가있는 경우 notifyAll ()을 사용하여 모든 스레드를 깨워 각 작업 개체를 하나씩 처리 할 수 ​​있습니다.

사용 가능한 작업 개체가 하나 뿐인 경우 모든 소비자 개체를 깨워 해당 개체에 대해 경쟁하는 요점은 무엇입니까? 사용 가능한 작업을 확인하는 첫 번째 작업은 작업을 수행하고 다른 모든 스레드는 수행 할 작업이 없는지 확인합니다.

나는 여기서 훌륭한 설명을 발견했다 . 한마디로 :

notify () 메소드는 일반적으로 자원 을 가져 오는 임의의 수의 "소비자"또는 "작업자"가있는 자원 풀에 사용되지만 자원이 풀에 추가되면 대기중인 소비자 또는 작업자 중 하나만 처리 할 수 ​​있습니다. 그것으로. notifyAll () 메소드는 실제로 대부분의 다른 경우에 사용됩니다. 엄밀히 말하면, 웨이터에게 여러 웨이터가 진행할 수있는 상태를 알리는 것이 필요합니다. 그러나 이것은 종종 알기가 어렵다. 따라서 일반적으로 notify () 사용에 대한 특정 논리가없는 경우 notifyAll ()을 사용해야합니다 . 왜냐하면 특정 객체에서 어떤 스레드가 어떤 스레드를 대기하는지, 왜 그런지 정확히 알기가 어렵 기 때문입니다.


동시성 유틸리티 당신은 또한 사이에 선택의 여지가 있음을 참고 signal()하고 signalAll()이러한 방법이있다라고한다. 따라서 질문은로도 유효합니다 java.util.concurrent.

더그 레아는 그의 흥미로운 점납니다 유명한 책을 하십시오 경우 notify()와는 Thread.interrupt()같은 시간에 일어나는는 실제로 길을 잃을 수도 통지합니다. notifyAll()오버 헤드 가격을 지불하더라도 (대부분의 스레드를 너무 많이 사용하더라도) 이러한 상황이 발생할 수 있고 극적인 의미를 갖는 것이 더 안전한 선택입니다.


Joshua Bloch에서 Effective Java 2nd edition의 Java Guru 자신 :

"항목 69 : 대기 및 통지를위한 동시성 유틸리티 선호"


다음은 예입니다. 그것을 실행하십시오. 그런 다음 notifyAll () 중 하나를 notify ()로 변경하고 어떻게되는지 확인하십시오.

ProducerConsumerExample 클래스

public class ProducerConsumerExample {

    private static boolean Even = true;
    private static boolean Odd = false;

    public static void main(String[] args) {
        Dropbox dropbox = new Dropbox();
        (new Thread(new Consumer(Even, dropbox))).start();
        (new Thread(new Consumer(Odd, dropbox))).start();
        (new Thread(new Producer(dropbox))).start();
    }
}

Dropbox 클래스

public class Dropbox {

    private int number;
    private boolean empty = true;
    private boolean evenNumber = false;

    public synchronized int take(final boolean even) {
        while (empty || evenNumber != even) {
            try {
                System.out.format("%s is waiting ... %n", even ? "Even" : "Odd");
                wait();
            } catch (InterruptedException e) { }
        }
        System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
        empty = true;
        notifyAll();

        return number;
    }

    public synchronized void put(int number) {
        while (!empty) {
            try {
                System.out.println("Producer is waiting ...");
                wait();
            } catch (InterruptedException e) { }
        }
        this.number = number;
        evenNumber = number % 2 == 0;
        System.out.format("Producer put %d.%n", number);
        empty = false;
        notifyAll();
    }
}

소비자 클래스

import java.util.Random;

public class Consumer implements Runnable {

    private final Dropbox dropbox;
    private final boolean even;

    public Consumer(boolean even, Dropbox dropbox) {
        this.even = even;
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            dropbox.take(even);
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) { }
        }
    }
}

프로듀서 클래스

import java.util.Random;

public class Producer implements Runnable {

    private Dropbox dropbox;

    public Producer(Dropbox dropbox) {
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            int number = random.nextInt(10);
            try {
                Thread.sleep(random.nextInt(100));
                dropbox.put(number);
            } catch (InterruptedException e) { }
        }
    }
}

짧은 요약:

항상 선호 의 notifyAll ()을 통해 () 통지 는 많은 수의 스레드가 모두 같은 일을 할 대규모 병렬 응용 프로그램이없는 경우.

설명:

notify () [...]는 단일 스레드를 깨 웁니다. notify () 를 사용하면 깨어 난 스레드를 지정할 수 없기 때문에 대규모 병렬 응용 프로그램, 즉 많은 수의 스레드가있는 프로그램에서 모두 유사한 작업을 수행하는 경우에만 유용합니다. 이러한 응용 프로그램에서는 깨어 난 스레드를 신경 쓰지 않습니다.

출처 : https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

위에서 설명한 상황에서 스레드가 동일한 작업을 수행하는 대규모 병렬 응용 프로그램에서 notify ()notifyAll ()비교하십시오 . 당신이 호출하는 경우 의 notifyAll ()를 하는 경우 의 notifyAll () , 하나의 스레드 만 실제로이 부여됩니다, 즉 스레드를 진행할 수 있기 때문에 (불필요하게 그들 중 많은 스레드의 거대한 숫자의 깨어 (즉, 스케줄링)을 유도 할 것이다 wait () , notify () 또는 notifyAll () 이 호출 된 오브젝트를 모니터하여 컴퓨팅 자원을 낭비합니다.

당신이 스레드의 거대한 숫자가 동시에 같은 일을하는 응용 프로그램이없는 경우에 따라서 선호 의 notifyAll ()가 이상 통지 () . 왜? 다른 사용자가이 포럼에서 이미 답변 했으므로 notify ()

이 객체의 모니터에서 대기중인 단일 스레드를 깨 웁니다. [...] 선택은 임의적 이며 구현의 재량에 따라 발생합니다.

출처 : Java SE8 API ( https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- )

소비자가 소비 할 준비가 된 (즉, wait () ing) 생산자가 준비 할 수있는 (즉, wait () ing) 품목의 생산 (소비 / 소비)이 비어 있는 생산자 소비자 애플리케이션이 있다고 가정하십시오 . 이 경우, (가) 통지 까지 깨울되는 선택이기 때문에 단지 소비자와 결코 생산을 깨울 수있는 임의의 . 생산자와 소비자가 각각 생산하고 소비 할 준비가되어 있지만 생산자 소비자주기는 진전을 보이지 않을 것입니다. 대신, 소비자는 깨어났습니다 (즉, wait () 상태를 떠나는 것 ). 비어 있기 때문에 항목을 대기열에서 꺼내지 않으며 다른 소비자는 notify () 를 진행합니다.

반대로 notifyAll () 은 생산자와 소비자를 모두 깨 웁니다. 스케줄 된 사람의 선택은 스케줄러에 따라 다릅니다. 물론 스케줄러의 구현에 따라 스케줄러는 컨슈머 만 스케줄 할 수 있습니다 (예 : 컨슈머 스레드에 우선 순위가 매우 높은 경우). 그러나 여기서는 스케줄러가 소비자 만 스케줄링하는 위험이 합리적으로 구현 된 스케줄러가 임의의 결정을 내리지 않기 때문에 JVM 만 소비자를 깨우는 위험보다 낮다는 가정이 있습니다 . 오히려, 대부분의 스케줄러 구현은 기아를 방지하기 위해 최소한의 노력을 기울입니다.


나는 아무도 악명 높은 "잃어버린 깨어 난"문제 (Google 그것)를 언급하지 않은 것에 매우 놀랐습니다.

원래:

  1. 동일한 조건에서 여러 스레드가 대기중인 경우
  2. 상태 A에서 상태 B로 전환 할 수있는 다중 스레드
  3. 상태 B에서 상태 A로 전환 할 수있는 여러 스레드 (보통 1과 동일한 스레드)
  4. 상태 A에서 B로 전환하면 1의 스레드에 알려야합니다.

그런 다음 손실 된 웨이크 업이 불가능하다는 확실한 보장이 없으면 notifyAll을 사용해야합니다.

일반적인 예는 동시 FIFO 대기열입니다. 여기에서 여러 대기열 (1 및 3)이 대기열을 비어있는 상태에서 비어 있지 않은 여러 대기열 (2 이상)로 전환 할 수 있습니다. -> 비어 있지 않은 경우 dequeuers에 알려야합니다.

빈 대기열에서 시작하여 2 개의 대기열 관리자와 2 개의 대기열 제거자가 상호 작용하고 1 대기열이 대기 상태로 유지되는 작업 인터리빙을 쉽게 작성할 수 있습니다.

교착 상태 문제와 비교할 수있는 문제입니다.


이것이 의심의 여지가 없어지기를 바랍니다.

notify () : notify () 메소드는 잠금을 기다리는 하나의 스레드 (해당 잠금에서 wait ()를 호출 한 첫 번째 스레드)를 깨 웁니다.

notifyAll () : notifyAll () 메소드는 잠금을 기다리는 모든 스레드를 깨 웁니다. JVM은 잠금 대기중인 스레드 목록에서 스레드 중 하나를 선택하고 해당 스레드를 깨 웁니다.

단일 스레드 가 잠금을 기다리는 경우 notify ()와 notifyAll () 사이에는 큰 차이가 없습니다. 그러나 잠금을 기다리는 스레드가 두 개 이상인 경우 notify () 및 notifyAll ()에서 깨어 난 정확한 스레드 는 JVM의 제어하에 있으며 특정 스레드 깨우기를 프로그래밍 방식으로 제어 할 수 없습니다.

언뜻 보면, 하나의 스레드를 깨우기 위해 notify ()를 호출하는 것이 좋습니다. 모든 스레드를 깨울 필요가없는 것 같습니다. 그러나 notify () 의 문제 는 스레드 깨우기가 깨어나기에 적합하지 않을 수 있다는 것입니다 (스레드가 다른 조건을 기다리는 중이거나 해당 스레드에 대한 조건이 여전히 충족되지 않음). 이 경우 notify ()가 손실 될 수 있으며 다른 스레드가 깨어 잠재적으로 교착 상태 유형으로 이어질 수 있습니다 (알림이 손실되고 다른 모든 스레드가 계속 알림을 기다리고 있음).

이 문제를 피하려면 잠금을 기다리는 스레드가 둘 이상 (또는 대기가 수행되는 둘 이상의 조건) 일 때 항상 notifyAll ()을 호출하는 것이 좋습니다. notifyAll () 메소드는 모든 스레드를 깨우므로 매우 효율적이지 않습니다. 그러나이 성능 손실은 실제 응용 프로그램에서는 무시할 수 있습니다.


스레드에는 세 가지 상태가 있습니다.

  1. WAIT-스레드가 CPU주기를 사용하지 않습니다
  2. 차단됨-스레드가 모니터를 확보하려고 시도하는 동안 차단되었습니다. CPU주기를 계속 사용 중일 수 있습니다.
  3. RUNNING-스레드가 실행 중입니다.

이제 notify ()가 호출되면 JVM은 하나의 스레드를 선택하여 모니터 오브젝트에 대한 경쟁이 없으므로 BLOCKED 상태로 RUNNING 상태로 이동합니다.

notifyAll ()이 호출되면 JVM은 모든 스레드를 선택하고 모든 스레드를 BLOCKED 상태로 이동합니다. 이러한 모든 스레드는 우선 순위에 따라 객체의 잠금을 얻습니다. 먼저 모니터를 얻을 수있는 스레드는 먼저 RUNNING 상태로 전환 될 수 있습니다.


notify()하나의 스레드를 notifyAll()깨우고 모든 스레드를 깨 웁니다. 내가 아는 한 중간 근거가 없습니다. 그러나 notify()스레드에 어떤 영향 을 줄지 잘 모르는 경우을 사용하십시오 notifyAll(). 매번 매력처럼 작동합니다.


내가 말할 수있는 한 위의 모든 대답은 정확하므로 다른 것을 알려 드리겠습니다. 프로덕션 코드의 경우 실제로 java.util.concurrent의 클래스를 사용해야합니다. 자바의 동시성 영역에서 그들은 당신을 위해 할 수있는 것이 거의 없습니다.


notify()보다 효율적인 코드를 작성할 수 있습니다 notifyAll().

여러 병렬 스레드에서 실행되는 다음 코드 조각을 고려하십시오.

synchronized(this) {
    while(busy) // a loop is necessary here
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notifyAll();
}

다음을 사용하여보다 효율적으로 만들 수 있습니다 notify().

synchronized(this) {
    if(busy)   // replaced the loop with a condition which is evaluated only once
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notify();
}

스레드 수가 많거나 대기 루프 조건을 평가하는 데 비용이 많이 드는 notify()경우보다 훨씬 빠릅니다 notifyAll(). 예를 들어, 1000 개의 스레드가있는 경우 첫 번째 notifyAll(), 998, 997 등의 후에 999 개의 스레드가 활성화되어 평가됩니다. 반대로 notify()솔루션을 사용하면 하나의 스레드 만 깨울 수 있습니다.

notifyAll()다음에 작업 할 스레드를 선택해야 할 때 사용하십시오 .

synchronized(this) {
    while(idx != last+1)  // wait until it's my turn
        wait();
}
...
synchronized(this) {
    last = idx;
    notifyAll();
}

마지막으로의 경우 깨어 난 블록 notifyAll()내부의 코드 synchronized가 한꺼번에 실행되는 것이 아니라 순차적으로 실행 된다는 것을 이해하는 것이 중요합니다 . 위의 예제에서 3 개의 스레드가 대기 중이고 네 번째 스레드가 호출한다고 가정 해 봅시다 notifyAll(). 세 스레드는 모두 깨어나지 만 하나만 실행을 시작하고 while루프 상태를 확인합니다 . 조건이 true인 경우 wait()다시 호출 하고 두 번째 스레드 만 실행을 시작하고 while루프 조건 을 확인합니다 .


더 간단한 설명은 다음과 같습니다.

notify ()를 사용하든 notifyAll ()을 사용하든 관계없이 정확히 하나의 다른 스레드가 모니터를 가져 와서 실행하기 시작합니다. (일부 스레드가이 객체에 대해 wait ()에서 실제로 차단되었다고 가정하면 관련없는 다른 스레드가 사용 가능한 모든 코어를 흡수하지는 않습니다.) 영향은 나중에 발생합니다.

스레드 A, B 및 C가이 오브젝트를 기다리고 있다고 가정하고 스레드 A가 모니터를 가져옵니다. 차이점은 A가 모니터를 놓으면 어떻게 되는가에 있습니다. notify ()를 사용한 경우 B와 C는 여전히 wait ()에서 차단됩니다. 모니터에서 대기하지 않고 통지를 기다리고 있습니다. A가 모니터를 놓아도 B와 C는 여전히 거기에 앉아 notify ()를 기다립니다.

notifyAll ()을 사용한 경우 B와 C는 모두 "알림 대기"상태를 지나서 모니터를 얻기 위해 대기 중입니다. A가 모니터를 해제하면 B 또는 C가 모니터를 획득하고 (해당 모니터와 경쟁하는 다른 스레드가 없다고 가정) 실행을 시작합니다.


이 답변은 eran의 주석을 포함하여 xagyg 의 탁월한 답변을 그래픽으로 다시 작성하고 단순화 한 것입니다 .

각 제품이 단일 소비자 용인 경우에도 notifyAll을 사용하는 이유는 무엇입니까?

다음과 같이 단순화 된 생산자와 소비자를 고려하십시오.

생산자:

while (!empty) {
   wait() // on full
}
put()
notify()

소비자:

while (empty) {
   wait() // on empty
}
take()
notify()

크기 1의 버퍼를 공유하는 2 명의 생산자와 2 명의 소비자를 가정하십시오. 다음 그림은 교착 상태로 이어지는 시나리오를 보여줍니다 . 모든 스레드가 notifyAll을 사용하는 경우 피할 수 있습니다 .

각 알림에는 스레드가 해제 된 레이블이 붙어 있습니다.

통지로 인한 교착 상태


에서 촬영 블로그 효과적인 자바 :

The notifyAll method should generally be used in preference to notify. 

If notify is used, great care must be taken to ensure liveness.

그래서, 내가 이해하는 것은 (위의 블로그에서 허용되는 답변 과 Java docs에 대한 "Yann TM"의 주석 )입니다.

  • notify () : JVM이이 오브젝트에서 대기중인 스레드 중 하나를 깨 웁니다. 실 선택은 공정하지 않고 임의로 이루어집니다. 따라서 동일한 스레드를 반복해서 깨울 수 있습니다. 따라서 시스템의 상태는 변경되지만 실제 진행은 이루어지지 않습니다. 따라서 라이브 록을 만듭니다.
  • notifyAll () : JVM이 모든 스레드를 깨운 다음이 스레드에 대한 잠금을 위해 모든 스레드가 경쟁합니다. 이제 CPU 스케줄러는이 객체에 대한 잠금을 획득하는 스레드를 선택합니다. 이 선택 프로세스는 JVM에 의한 선택보다 훨씬 낫습니다. 따라서 생기를 보장합니다.

@xagyg가 게시 한 코드를 살펴보십시오.

: 가정 두 개의 서로 다른 스레드가 서로 다른 두 가지 조건을 기다리고 있습니다 첫 번째 스레드가 기다리고 있습니다 , 그리고 두 번째 스레드가 기다리고 있습니다 .
buf.size() != MAX_SIZEbuf.size() != 0

어떤 시점 buf.size() 에서 0과 같지 않다고 가정하십시오 . notify()대신 JVM이 호출 notifyAll()되고 첫 번째 스레드에 알림이 표시됩니다 (두 번째 스레드가 아님).

첫 번째 스레드가 깨어나서 buf.size()어느 것이 리턴되는지 점검 한 MAX_SIZE후 대기 상태로 돌아갑니다. 두 번째 스레드는 깨어나지 않고 계속 기다렸다가 호출하지 않습니다 get().


실제로 Java Concurrency에서 설명하는 내용에 대해 언급하고 싶습니다.

첫째, Notify 또는 NotifyAll?

It will be NotifyAll, and reason is that it will save from signall hijacking.

두 개의 스레드 A와 B가 동일한 조건 큐의 다른 조건 술어를 대기하고 있으며 통지가 호출되면 스레드 JVM이 통지하는 것은 JVM에 달려 있습니다.

통지가 스레드 A 및 JVM 통지 스레드 B에 대한 것이면 스레드 B가 깨어나고이 알림이 유용하지 않다는 것을 확인하여 다시 기다립니다. 그리고 스레드 A는이 누락 된 신호에 대해 절대 알게되지 않으며 누군가가 알림을 도용했습니다.

따라서 notifyAll을 호출하면이 문제가 해결되지만 모든 스레드에 알리고 모든 스레드가 동일한 잠금을 위해 경쟁하므로 컨텍스트 전환과 CPU 부하가 발생하므로 성능에 영향을 미칩니다. 그러나 우리는 올바르게 동작하는 경우에만 성능에주의를 기울여야합니다. 만약 동작 자체가 정확하지 않으면 성능을 사용할 수 없습니다.

이 문제는 각 조건 술어에 대해 다른 대기를 제공하므로 jdk 5에 제공된 명시 적 잠금 잠금의 Condition 오브젝트를 사용하여 해결할 수 있습니다. 여기서는 올바르게 작동하고 신호를 호출하고 하나의 스레드 만 해당 조건을 기다리고 있는지 확인하므로 성능 문제가 없습니다.


notify()wait()동일한 객체에서 호출 첫 번째 스레드를 깨 웁니다 .

notifyAll()wait()동일한 객체에서 호출 모든 스레드를 깨 웁니다 .

우선 순위가 가장 높은 스레드가 먼저 실행됩니다.


notify는 대기 상태에있는 하나의 스레드 만 알리는 반면, all은 모든 대기 스레드의 모든 스레드에 알리고 모든 알림 스레드와 모든 차단 된 스레드는 잠금을받을 수 있습니다. 대기 상태에있는 사람을 포함한 다른 모든 사람은 차단 상태에있게됩니다.


위의 훌륭한 상세 설명과 내가 생각할 수있는 가장 간단한 방법을 요약하면 이는 1) 전체 동기화 장치 (블록 또는 오브젝트)에서 획득 한 JVM 내장 모니터의 한계 때문입니다. 대기 / 알려지는 특정 조건에 대해 차별하지 않습니다.

이는 여러 스레드가 서로 다른 조건에서 대기하고 notify ()가 사용되는 경우 선택한 스레드가 새로 이행 된 조건에서 진행되는 스레드가 아닐 수 있으며 해당 스레드 (및 현재 대기중인 다른 스레드) 진행할 수없는 상태, 그리고 결국 굶주림이나 프로그램 끊기를 조건 등을 충족시키기 위해.

대조적으로, notifyAll ()은 모든 대기 스레드가 결국 잠금을 다시 획득하고 각각의 상태를 점검 할 수있게하여 결국 진행이 가능하게합니다.

따라서 대기 스레드가 선택 될 때 진행이 가능하도록 보장 된 경우에만 notify ()를 안전하게 사용할 수 있습니다. 일반적으로 동일한 모니터 내의 모든 스레드가 하나의 동일한 조건 만 검사 할 때 만족됩니다. 실제 응용 분야의 경우.


notify()-오브젝트의 대기 세트에서 임의 스레드를 선택하여 BLOCKED상태로 만듭니다. 오브젝트의 대기 세트에있는 나머지 스레드는 여전히 WAITING상태입니다.

notifyAll()-오브젝트의 대기 세트에서 모든 스레드를 BLOCKED상태로 이동합니다. 을 사용한 후에 notifyAll()는 공유 객체의 대기 세트에 스레드가 남아 있지 않습니다. 모든 스레드가 현재 BLOCKED상태에 있고 상태 가 아니기 때문 WAITING입니다.

BLOCKED-잠금 획득을 위해 차단되었습니다. WAITING-통지 대기 중 (또는 참여 완료를 위해 차단됨).


"객체"의 wait ()를 호출하면 (객체 잠금이 획득 될 것으로 예상) 인턴은 해당 객체에 대한 잠금을 해제하고 다른 스레드가이 "객체"에 대한 잠금을 갖도록 도와줍니다. "리소스 / 오브젝트"를 기다리는 하나 이상의 스레드 (다른 스레드를 고려하면 동일한 위의 오브젝트에 대한 대기를 발행하고 리소스 / 오브젝트를 채우고 notify / notifyAll을 호출하는 스레드가있는 방식으로 다운을 발행 함)

여기에서 동일한 객체 (프로세스 / 코드의 동일 / 다른 쪽에서)에 대한 알림을 발행하면 차단 된 대기중인 단일 스레드 (모든 대기 스레드가 아님)가 해제됩니다.이 릴리스 된 스레드는 JVM 스레드에 의해 선택됩니다. 개체의 스케줄러 및 모든 잠금 획득 프로세스는 일반과 동일합니다.

이 객체에서 공유 / 작업 할 스레드가 하나만있는 경우 wait-notify 구현에서 notify () 메소드 만 사용하는 것이 좋습니다.

비즈니스 로직을 기반으로 둘 이상의 스레드가 리소스 / 오브젝트를 읽고 쓰는 상황 인 경우 notifyAll ()

이제 객체에서 notify ()를 발행 할 때 jvm이 정확히 어떻게 대기 스레드를 식별하고 중단하는지 찾고 있습니다 ...


위의 확실한 대답이 있지만, 내가 읽은 혼란과 오해의 수에 놀랐습니다. 이것은 아마도 깨진 동시 코드를 작성하는 대신 가능한 한 java.util.concurrent를 사용해야한다는 아이디어를 증명합니다. 질문으로 돌아가서 요약하자면 오늘날 모범 사례는 깨어 난 문제로 인해 모든 상황에서 notify ()를 피하는 것입니다. 이것을 이해하지 못하는 사람은 미션 크리티컬 동시성 코드를 작성할 수 없습니다. 방목 문제가 걱정되는 경우 한 번에 하나의 스레드를 깨우는 안전한 방법 중 하나는 다음과 같습니다. 1. 대기 스레드에 대한 명시적인 대기 큐를 작성합니다. 2. 대기열에있는 각 스레드가 선행 작업을 기다리도록합니다. 3. 각 스레드 호출이 완료되면 notifyAll ()을 호출하십시오. 또는 Java.util.concurrent. *를 사용할 수 있습니다.


모든 것을 깨우는 것이 여기서 중요하지 않습니다. 통지 및 통지 대기,이 모든 것은 객체의 모니터를 소유 한 후에 놓입니다. 스레드가 대기 단계에 있고 알림이 호출되면이 스레드는 잠금을 수행하며 해당 지점의 다른 스레드는 해당 잠금을 수행 할 수 없습니다. 따라서 동시 액세스가 불가능합니다. 내가 아는 한 통지 및 notifyall 대기는 객체를 잠근 후에 만 ​​알릴 수 있습니다. 내가 틀렸다면 나를 바로 잡으십시오.

참고 URL : https://stackoverflow.com/questions/37026/java-notify-vs-notifyall-all-over-again



반응형