문제

구글에서 "차이점"이라고 검색하면 notify() 그리고 notifyAll()" 그러면 많은 설명이 나타날 것입니다(javadoc 단락을 제외하고).그것은 모두 깨어나고 있는 대기 스레드의 수로 귀결됩니다.하나에 notify() 그리고 모두 notifyAll().

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

무엇입니까? 유용한 사이의 차이 통지() 그리고 통지모두() 그 다음에?뭔가 빠졌나요?

도움이 되었습니까?

해결책

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

그것은 정확하지 않습니다. o.notifyAll() 깨어나다 모두 차단된 스레드 중 o.wait() 전화.스레드는 다음에서만 반환될 수 있습니다. o.wait() 하나씩, 그러나 그들은 각각 ~ 할 것이다 차례를 잡습니다.


간단히 말해서 스레드가 알림을 기다리는 이유에 따라 다릅니다.대기 중인 스레드 중 하나에게 어떤 일이 발생했음을 알리고 싶나요, 아니면 동시에 모든 스레드에 알리고 싶나요?

어떤 경우에는 대기가 끝나면 모든 대기 스레드가 유용한 조치를 취할 수 있습니다.예를 들어 특정 작업이 완료되기를 기다리는 스레드 집합이 있습니다.작업이 완료되면 대기 중인 모든 스레드는 해당 작업을 계속할 수 있습니다.그런 경우에는 다음을 사용합니다. 통지모두() 대기 중인 모든 스레드를 동시에 깨울 수 있습니다.

상호 배타적 잠금과 같은 또 다른 경우에는 대기 중인 스레드 중 하나만 알림을 받은 후 유용한 작업을 수행할 수 있습니다(이 경우 잠금 획득).그런 경우에는 차라리 통지().올바르게 구현하면 ~할 수 있었다 사용 통지모두() 이 상황에서도 마찬가지지만 어쨌든 아무것도 할 수 없는 스레드를 불필요하게 깨울 수 있습니다.


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

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

그렇게 하면 만약에 o.notifyAll() 호출은 하나 이상의 대기 스레드를 깨우고, 그 스레드에서 가장 먼저 반환되는 스레드입니다. o.wait() 조건을 false 상태로 유지하면 깨어난 다른 스레드는 다시 대기 상태로 돌아갑니다.

다른 팁

분명히, notify 대기 세트에서 하나의 스레드를 깨우고, notifyAll 대기 세트의 모든 스레드를 깨웁니다.다음 토론을 통해 모든 의심이 해소될 것입니다. notifyAll 대부분의 시간 동안 사용해야 합니다.어떤 것을 사용해야 할지 모르겠다면 다음을 사용하세요. notifyAll.다음 설명을 참조하세요.

매우 주의 깊게 읽고 이해하십시오.질문이 있으시면 이메일을 보내주세요.

생산자/소비자를 살펴보세요(두 가지 메서드가 있는 ProducerConsumer 클래스라고 가정).IT IS BROKEN (사용하기 때문에) 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이 잠금을 획득합니다.추측해 보세요. 운이 좋게도 while 루프는 C1이 루프 검사(보호)를 수행하고 존재하지 않는 요소를 버퍼에서 제거하는 것을 방지하기 때문입니다(C2는 이미 그것을 얻었습니다!).우리가 없었다면 while, 우리는 IndexArrayOutOfBoundsException C1이 버퍼에서 첫 번째 요소를 제거하려고 시도합니다!

지금,

자, 이제 왜 informAll이 필요한가요?

위의 생산자/소비자 예에서는 다음과 같이 해도 괜찮을 것 같습니다. 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 - 대기 루프를 확인합니다 - 이미 문자입니다 - 대기합니다

4단계:
- C1은 문자 1개를 얻으려고 시도합니다.
- C2는 1개의 문자를 얻으려고 시도합니다. get 방법
- C3는 1개의 문자를 얻으려고 시도합니다. get 방법

5단계:
- C1이 다음을 실행하고 있습니다. get 방법 - 문자를 가져오고 호출합니다. notify, 메소드 종료
- notify P2를 깨운다
- 그러나 P2가 잠금을 다시 획득해야 하기 전에 C2가 메소드에 진입하므로 P2는 해당 메소드에 진입하는 것을 차단합니다. put 방법
- C2는 대기 루프를 확인하고 버퍼에 더 이상 문자가 없으므로 대기합니다.
- C3은 C2 다음에 메소드를 시작하지만 P2 전에는 대기 루프를 확인하고 버퍼에 더 이상 문자가 없으므로 대기합니다.

6단계:
- 지금:P3, C2, C3가 기다리고 있어요!
- 마지막으로 P2는 잠금을 획득하고, 버퍼에 문자를 넣고, 알림을 호출하고, 메서드를 종료합니다.

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

해결책:바꾸다 notify ~와 함께 notifyAll 생산자/소비자 코드(위).

유용한 차이점:

  • 사용 통지() 모든 대기 스레드가 상호 교환 가능한 경우(깨어나는 순서는 중요하지 않음) 또는 대기 스레드가 하나만 있는 경우.일반적인 예는 대기열에서 작업을 실행하는 데 사용되는 스레드 풀입니다. 작업이 추가되면 스레드 중 하나에 깨어나 다음 작업을 실행하고 다시 절전 모드로 전환하라는 알림이 전달됩니다.

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

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

사용 가능한 작업 개체가 하나만 있는 경우 모든 소비자 개체를 깨워 해당 개체를 놓고 경쟁하는 것이 무슨 의미가 있습니까?사용 가능한 작업을 확인하는 첫 번째 스레드가 이를 가져오고 다른 모든 스레드는 확인하여 수행할 작업이 없음을 찾습니다.

나는 찾았다 여기에 훌륭한 설명이 있습니다.간단히 말해서:

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

동시성 유틸리티를 사용하면 다음 중에서 선택할 수도 있습니다. signal() 그리고 signalAll() 이러한 메소드가 거기에서 호출되기 때문입니다.따라서 질문은 다음과 같은 경우에도 유효합니다. java.util.concurrent.

Doug Lea는 그의 책에서 흥미로운 점을 제시합니다. 유명한 책:만약 notify() 그리고 Thread.interrupt() 동시에 발생하면 알림이 실제로 손실될 수 있습니다.이런 일이 일어날 수 있고 극적인 영향을 미칠 경우 notifyAll() 오버헤드 비용을 지불하더라도(대부분의 경우 너무 많은 스레드를 깨우기 때문에) 더 안전한 선택입니다.

Effective Java 2판의 Java 전문가인 Joshua Bloch가 쓴 내용은 다음과 같습니다.

"항목 69:기다리고 알리는 것보다 동시성 유틸리티를 선호합니다."

여기에 예가 있습니다.실행하세요.그런 다음 informAll() 중 하나를 inform()으로 변경하고 무슨 일이 일어나는지 확인하십시오.

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();
    }
}

드롭박스 수업

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

짧은 요약:

항상 선호 통지모두() ~ 위에 통지() 많은 수의 스레드가 모두 동일한 작업을 수행하는 대규모 병렬 애플리케이션이 없다면 말이죠.

설명:

통지() ...] 단일 스레드를 깨우십시오.왜냐하면 통지() 깨어 난 스레드를 지정할 수는 없으며, 대규모 병렬 응용 프로그램, 즉 많은 스레드가있는 프로그램, 모두 비슷한 집안일을 수행하는 데 유용합니다.이러한 애플리케이션에서는 어떤 스레드가 깨어나는지 신경 쓰지 않습니다.

원천: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

비교하다 통지() ~와 함께 통지모두() 위에서 설명한 상황에서:스레드가 동일한 작업을 수행하는 대규모 병렬 애플리케이션입니다.전화하면 통지모두() 그 경우에는 통지모두() 깨어나도록 유도합니다(예:스케줄링) 수많은 스레드 중 다수가 불필요하게 발생합니다(실제로는 하나의 스레드, 즉 객체에 대한 모니터 권한을 부여받을 스레드만 진행할 수 있기 때문). 기다리다(), 통지(), 또는 통지모두() 호출됨) 따라서 컴퓨팅 리소스가 낭비됩니다.

따라서, 엄청난 수의 스레드가 동일한 작업을 동시에 수행하는 애플리케이션이 없다면 다음을 선호하세요. 통지모두() ~ 위에 통지().왜?왜냐하면 다른 사용자들이 이미 이 포럼에 답변했기 때문입니다. 통지()

이 개체의 모니터를 기다리고 있는 단일 스레드를 깨웁니다....] 선택은입니다 임의의 이행의 재량에 따라 발생합니다.

원천:자바 SE8 API(https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)

소비자가 준비된 생산자 소비자 애플리케이션이 있다고 상상해 보십시오(예: 기다리다() ing) 소비할 준비가 되면 생산자는 준비가 됩니다(즉, 기다리다() ing)을 생산하고 항목(생산/소비) 대기열이 비어 있습니다.그 경우, 통지() 소비자만 깨울 수 있고 생산자는 깨울 수 없습니다. 왜냐하면 깨어나는 사람을 선택하는 것은 임의의.생산자와 소비자가 각각 생산하고 소비할 준비가 되어 있어도 생산자 소비자 순환은 아무런 진전을 이루지 못합니다.대신 소비자가 깨어납니다(예:떠나는 기다리다() 상태), 항목이 비어 있기 때문에 대기열에서 항목을 가져오지 않습니다. 통지() 계속 진행하려면 또 다른 소비자가 필요합니다.

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

나는 악명 높은 "깨어남 상실" 문제에 대해 아무도 언급하지 않았다는 사실에 매우 놀랐습니다(구글에서 검색).

원래:

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

그런 다음 웨이크업 손실이 불가능하다는 것을 입증할 수 있는 경우가 아니면 informAll을 사용해야 합니다.

일반적인 예는 다음과 같은 동시 FIFO 대기열입니다.다중 대기열자(1.그리고 3.위)은 큐를 비어 비어 있지 않은 다중 데키어로 전환 할 수 있습니다 (2.위) 조건을 기다릴 수 있습니다.

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

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

이것이 약간의 의심을 없애기를 바랍니다.

통지() :Notify () 메소드는 잠금을 기다리는 하나의 스레드 (해당 잠금에서 Wait ()라고 불리는 첫 번째 스레드)를 깨우냅니다.

통지모두() :informAll() 메소드는 잠금을 기다리고 있는 모든 스레드를 깨웁니다.JVM은 잠금 장치를 기다리는 스레드 목록에서 스레드 중 하나를 선택하고 스레드 위로 깨어납니다.

단일 스레드의 경우 잠금을 기다리는 동안에는 inform()과 informAll() 사이에 큰 차이가 없습니다.그러나 잠금을 기다리는 스레드가 두 개 이상인 경우 inform() 및 informAll() 모두에서 깨어나는 정확한 스레드는 다음과 같습니다. JVM의 제어 하에 그리고 특정 스레드를 깨우는 것을 프로그래밍 방식으로 제어할 수는 없습니다.

언뜻 보기에는 하나의 스레드를 깨우기 위해 단지 inform()을 호출하는 것이 좋은 생각인 것처럼 보입니다.모든 스레드를 깨우는 것이 불필요해 보일 수도 있습니다.그러나 문제는 통지()는 깨어난 스레드가 적합한 스레드가 아닐 수도 있다는 것입니다. 깨워야 합니다(스레드가 다른 조건을 기다리고 있거나 해당 스레드에 대한 조건이 여전히 충족되지 않는 경우 등). 그런 경우에는, inform()이 손실될 수 있으며 다른 스레드가 깨어나 잠재적으로 일종의 교착 상태로 이어질 수 없습니다(알림이 손실되고 다른 모든 스레드가 영원히 알림을 기다리고 있음).

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

notify() 그 동안 하나의 스레드를 깨울 것입니다. notifyAll() 모두 깨어날 것입니다.내가 아는 한 중간 지점은 없습니다.하지만 무엇인지 확실하지 않다면 notify() 당신의 스레드에 할 것입니다. notifyAll().매번 매력처럼 작동합니다.

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

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

통지()를 사용하든 통지 All()을 사용하든 즉각적인 결과는 정확히 하나의 다른 스레드가 모니터를 획득하고 실행을 시작한다는 것이 맞습니다.(일부 스레드가 실제로 이 개체에 대한 wait()에서 차단되었다고 가정하면 관련되지 않은 다른 스레드는 사용 가능한 모든 코어를 흡수하지 않습니다.) 영향은 나중에 발생합니다.

스레드 A, B, C가 이 개체를 기다리고 있었고 스레드 A가 모니터를 가져왔다고 가정합니다.차이점은 A가 모니터를 놓으면 어떤 일이 발생하는지에 있습니다.통지()를 사용한 경우 B와 C는 여전히 wait()에서 차단됩니다.그들은 모니터를 기다리는 것이 아니라 알림을 기다리고 있습니다.A가 모니터를 해제하면 B와 C는 여전히 거기에 앉아 통지()를 기다리고 있을 것입니다.

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

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

  1. WAIT - 스레드가 CPU 주기를 사용하고 있지 않습니다.
  2. BLOCKED - 스레드가 모니터를 획득하려고 차단되었습니다. 여전히 CPU 주기를 사용 중일 수 있습니다.
  3. RUNNING - 스레드가 실행 중입니다.

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

informAll()이 호출되면 JVM은 모든 스레드를 선택하고 모두 BLOCKED 상태로 이동합니다.이 모든 스레드는 우선순위에 따라 개체 잠금을 가져옵니다. 모니터를 먼저 획득할 수 있는 스레드는 먼저 RUNNING 상태로 갈 수 있으며 이런 식으로 계속됩니다.

이 답변은 다음과 같은 훌륭한 답변을 그래픽으로 다시 작성하고 단순화한 것입니다. xagyg, 님의 댓글 포함 에란.

각 제품이 단일 소비자를 대상으로 하는 경우에도 informAll을 사용하는 이유는 무엇입니까?

다음과 같이 단순화된 생산자와 소비자를 생각해 보세요.

생산자:

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

소비자:

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

2명의 생산자와 2명의 소비자가 크기 1의 버퍼를 공유한다고 가정합니다.다음 그림은 다음과 같은 시나리오를 보여줍니다. 이중 자물쇠, 모든 스레드가 사용되면 피할 수 있습니다. 모두에게 알리다.

각 알림에는 깨어나는 스레드가 표시되어 있습니다.

deadlock due to notify

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개의 스레드가 있는 경우 첫 번째 스레드 이후 999개의 스레드가 활성화되고 평가됩니다. notifyAll(), 998, 997 등으로 이어집니다.반대로, notify() 해결 방법을 사용하면 하나의 스레드만 깨어납니다.

사용 notifyAll() 다음에 작업을 수행할 스레드를 선택해야 하는 경우:

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

마지막으로 다음과 같은 경우를 이해하는 것이 중요합니다. notifyAll(), 내부 코드 synchronized 깨어난 블록은 한꺼번에 실행되지 않고 순차적으로 실행됩니다.위의 예에서 세 개의 스레드가 대기 중이고 네 번째 스레드가 호출한다고 가정해 보겠습니다. notifyAll().세 개의 스레드가 모두 활성화되지만 하나만 실행을 시작하고 상태를 확인합니다. while 고리.조건이 다음과 같은 경우 true, 그것은 전화할 것이다 wait() 그런 다음에만 두 번째 스레드가 실행을 시작하고 스레드를 확인합니다. while 루프 조건 등.

에서 가져옴 블로그 효과적인 Java에서:

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

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

그래서 제가 이해하는 것은 (앞서 언급한 블로그에서 "Yann TM"의 댓글입니다) 수락된 답변 그리고 자바 문서):

  • 알림() :JVM은 이 객체에서 대기 중인 스레드 중 하나를 깨웁니다.실선정은 공정성 없이 임의로 이루어집니다.따라서 동일한 스레드가 계속해서 깨어날 수 있습니다.따라서 시스템 상태는 변경되지만 실제 진행은 이루어지지 않습니다.따라서 라이브락.
  • 통지모두():JVM은 모든 스레드를 깨운 다음 ​​모든 스레드가 이 객체에 대한 잠금을 위해 경쟁합니다.이제 CPU 스케줄러는 이 개체에 대한 잠금을 획득하는 스레드를 선택합니다.이 선택 프로세스는 JVM에 의한 선택보다 훨씬 낫습니다.따라서 생명력을 보장합니다.

@xagyg가 게시한 코드를 살펴보세요.

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

어느 시점에 가정해보자 buf.size() 0이 아니다.JVM 호출 notify() 대신에 notifyAll(), 첫 번째 스레드에 알림이 전송됩니다(두 번째 스레드가 아님).

첫 번째 스레드가 깨어나서 다음 사항을 확인합니다. buf.size() 돌아올 수도 있는 것 MAX_SIZE, 다시 대기 상태로 돌아갑니다.두 번째 스레드는 깨어나지 않고 계속 대기하며 호출하지 않습니다. get().

notify() 호출한 첫 번째 스레드를 깨웁니다. wait() 같은 개체에.

notifyAll() 호출한 모든 스레드를 깨웁니다. wait() 같은 개체에.

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

Java Concurrency in Practice에 설명된 내용을 언급하고 싶습니다.

첫 번째 요점은 Notify 또는 NotifyAll입니까?

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

두 개의 스레드 A와 B가 다른 조건에서 대기 중이면 동일한 조건 큐의 홍보가 호출되면 Thread JVM이 알리는 JVM까지 올라갑니다.

이제 알림이 Thread A 및 JVM 알림 B에 대한 의미라면 스레드 B가 일어나서이 알림이 유용하지 않으므로 다시 기다릴 것임을 알 수 있습니다.그리고 Thread A는이 놓친 신호에 대해 알지 못하고 누군가는 그것을 납치했습니다.

따라서 NotifyAll을 호출하면이 문제가 해결되지만 다시 모든 스레드에 알리면 모든 스레드가 동일한 잠금에 대해 경쟁하므로 CPU의 컨텍스트 스위치가 포함됩니다.그러나 우리는 성능이 올바르게 행동하는 경우에만 성능에 관심을 가져야합니다. 행동 자체가 정확하지 않은 경우 성능은 사용하지 않습니다.

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

통지는 대기 상태에 있는 하나의 스레드에게만 알리고, 모든 통지는 대기 상태에 있는 모든 스레드에 통지합니다. 이제 통지된 모든 스레드와 차단된 모든 스레드는 잠금에 적합합니다. 그 중에서 하나만 잠금을 얻고 다른 모든 사람(이전에 대기 상태에 있던 사람 포함)은 차단된 상태가 됩니다.

notify() - 객체의 대기 세트에서 임의의 스레드를 선택하여 해당 스레드에 넣습니다. BLOCKED 상태.객체의 대기 세트에 있는 나머지 스레드는 여전히 WAITING 상태.

notifyAll() - 객체의 대기 세트에서 모든 스레드를 다음으로 이동합니다. BLOCKED 상태.사용 후 notifyAll(), 공유 객체의 대기 세트에 남아 있는 스레드가 없습니다. 이제 모든 스레드가 대기 상태에 있기 때문입니다. BLOCKED 상태가 아닌 상태 WAITING 상태.

BLOCKED - 잠금 획득을 위해 차단되었습니다.WAITING - 알림을 기다리는 중(또는 가입 완료를 위해 차단됨)

위의 훌륭하고 상세한 설명을 요약하자면, 제가 생각할 수 있는 가장 간단한 방법은 1) 전체 동기화 단위(블록 또는 객체)에서 획득하는 JVM 내장 모니터의 한계 때문이며 2) 이는 JVM 내장 모니터의 한계 때문입니다. 대기/알림/알림 중인 특정 조건을 차별하지 않습니다.

이는 여러 스레드가 서로 다른 조건에서 대기하고 있으며 inform()이 사용되는 경우 선택한 스레드가 새로 충족된 조건에서 진행을 수행하는 스레드가 아닐 수 있음을 의미합니다. 이로 인해 해당 스레드(및 현재 여전히 대기 중인 다른 스레드가 발생할 수 있음) 조건을 충족시키다 등등..) 진행을 할 수 없게 되어 결국 굶거나 프로그램이 중단되는 현상이 발생합니다.

이와 대조적으로, informAll()을 사용하면 대기 중인 모든 스레드가 결국 잠금을 다시 획득하고 해당 조건을 확인하여 결국 진행이 가능해집니다.

따라서 inform()은 대기 중인 스레드가 선택되어야 진행이 가능하도록 보장되는 경우에만 안전하게 사용할 수 있습니다. 이는 일반적으로 동일한 모니터 내의 모든 스레드가 하나의 동일한 조건만 확인할 때 만족됩니다. 실제 응용 프로그램의 경우입니다.

"객체"의 wait()를 호출하면(객체 잠금이 획득될 것으로 예상) 인턴은 해당 객체에 대한 잠금을 해제하고 다른 스레드가 이 "객체"에 대한 잠금을 갖도록 도와줍니다. 이 시나리오에서는 2개 이상의 스레드가 "리소스/객체"를 기다리고 있습니다(다른 스레드도 동일한 위 객체에 대해 대기를 실행했으며 리소스/객체를 채우고 통지/notifyAll을 호출하는 스레드가 있을 것이라는 점을 고려).

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

이 객체에 대해 공유/작업할 스레드가 하나만 있는 경우 대기 알림 구현에서 inform() 메서드만 사용하는 것이 좋습니다.

비즈니스 로직을 기반으로 둘 이상의 스레드가 리소스/객체를 읽고 쓰는 상황에 있다면 informAll()을 사용해야 합니다.

이제 객체에 대해 inform()을 실행할 때 jvm이 대기 스레드를 정확히 식별하고 중단하는 방법을 찾고 있습니다.

위에 몇 가지 확실한 답변이 있지만, 제가 읽은 수많은 혼란과 오해에 놀랐습니다.이것은 아마도 깨진 동시 코드를 직접 작성하는 대신 java.util.concurrent를 최대한 많이 사용해야 한다는 아이디어를 증명하는 것 같습니다.질문으로 돌아가서:요약하자면, 오늘날 가장 좋은 방법은 웨이크업 손실 문제로 인해 모든 상황에서 inform()을 피하는 것입니다.이것을 이해하지 못하는 사람은 미션 크리티컬 동시성 코드를 작성하는 것을 허용해서는 안 됩니다.군집 문제가 걱정되는 경우 한 번에 하나의 스레드를 깨우는 안전한 방법 중 하나는 다음과 같습니다.1.대기 스레드에 대한 명시적 대기 큐를 구축합니다.2.대기열의 각 스레드가 이전 스레드를 기다리도록 합니다.삼.완료되면 각 스레드가 informAll()을 호출하도록 합니다.또는 이미 이를 구현한 Java.util.concurrent.*를 사용할 수 있습니다.

여기서 모두 깨어난다는 것은 그다지 의미가 없습니다.wait inform 및 informall, 이들 모두는 객체의 모니터를 소유한 후에 배치됩니다.스레드가 대기 단계에 있고 알림이 호출되면 이 스레드가 잠금을 차지하며 해당 시점의 다른 스레드는 해당 잠금을 차지할 수 없습니다.따라서 동시 액세스가 전혀 발생할 수 없습니다.내가 아는 한 대기 알림 및 알림 전체에 대한 호출은 개체에 대한 잠금을 수행한 후에만 이루어질 수 있습니다.내가 틀렸다면 정정하십시오.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top