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


377

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

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

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


6
동시성에 사용할 유용한 라이브러리는 동시성 라이브러리에 있습니다. 나는 거의 모든 경우에 이것이 더 나은 선택이라고 제안합니다. 동시성 라이브러리 이전의 Java 5.0 (2004 년에 표준으로 추가
되었음

4
나는 피터와 동의하지 않는다. 동시성 라이브러리는 자바로 구현하고, 대신 좋은 오래된의 동시성 라이브러리를 사용하여 스스로 발을 쏠 수있는 등, 자바 코드를 많이, 당신은 자물쇠를 호출 할 때마다 ()를 실행 잠금 해제 ()가되어 synchronized일정을 제외하고, 오히려 드문 사용 사례입니다.
Alexander Ryzhov 2016 년

2
오해의 핵심은 다음과 같습니다. ... 추가 모니터 획득을 위해 항상 하나의 스레드 만 선택됩니다. 첫 번째 경우에는 VM에 의해 선택된 것, 두 번째 경우에는 시스템 스레드 스케줄러에 의해 선택된 것. 그 의미는 본질적으로 동일하다는 것입니다. 설명 된대로 동작은 정확하지만 빠진 것은 notifyAll()첫 번째 스레드 이후의 다른 스레드가 깨어 모니터를 하나씩 획득한다는 것입니다. 이 notify경우 다른 스레드도 깨우지 않습니다. 기능적으로는 매우 다릅니다!
BeeOnRope

1) 많은 스레드가 객체를 기다리고 있고 notify ()가 해당 객체에서 한 번만 호출됩니다. 대기 스레드 중 하나를 제외하고 나머지 스레드는 영원히 대기합니까? 2) notify ()를 사용하면 많은 대기 스레드 중 하나만 실행을 시작합니다. notifyall ()을 사용하면 대기중인 모든 스레드에 통보되지만 그중 하나만 실행되기 시작하므로 notifyall () 사용은 무엇입니까?
Chetan Gowda 2016 년

@ChetanGowda 모든 스레드 알림 대 정확히 하나의 임의의 스레드 만 실제로 미묘하게 보이지만 중요한 차이가 발생할 때까지 실제로 큰 차이가 있습니다. /신호. 모두에게 통지하면 모든 스레드가 추가 통지없이 순서대로 순서대로 실행되고 완료됩니다. 여기서는 스레드가 blocked있고 그렇지 않다고 말합니다 waiting. blocked다른 스레드가 sync블록 안에있을 때까지 exec가 일시적으로 일시 중단되면 .
user104309

답변:


248

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

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


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

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

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


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

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

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


29
하나의 스레드에만 알리지 만 여러 스레드가 객체를 기다리고있는 경우 VM은 어떤 스레드를 알릴지를 어떻게 결정합니까?
양서류

6
Java 사양에 대해 확신 할 수는 없지만 일반적으로 그러한 세부 사항에 대한 가정을 피해야합니다. 그래도 VM이 제정신이며 대부분 공정하게 수행 할 것이라고 생각할 수 있습니다.
Liedman

15
Liedman은 심각하지 않으며 Java 사양에 따르면 notify ()가 공정하지 않다고 명시되어 있습니다. 즉, 통지 할 각 호출이 동일한 스레드를 다시 깨울 수 있습니다 (모니터의 스레드 큐가 FAIR 또는 FIFO가 아님). 그러나 스케줄러는 공정해야합니다. 그렇기 때문에 스레드가 2 개 이상인 대부분의 경우 notifyAll을 선호해야합니다.
Yann TM

45
@YannTM 저는 건설적인 범죄에 대한 것이지만, 당신의 말투가 약간 불공평하다고 생각합니다. 나는 "특정하게 말할 수 없다"고 "생각한다"고 명시 적으로 말했습니다. 쉽게, 당신은 7 년 전에 100 % 정확하지 않은 것을 쓴 적이 있습니까?
Liedman

10
문제는 이것이 받아 들여지는 대답이며 개인적인 자부심의 문제는 아닙니다. 당신이 지금 틀렸다는 것을 알고 있다면, 답을 편집하여 말하십시오.
Yann TM

330

분명히, 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-대기 루프 확인-이미 문자-대기

4 단계 :
-C1이 1 개의 문자
를 가져 오려고 시도 함-C2가 1 개의 문자를 가져 오려고 시도 함- 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생산자 / 소비자 코드 (위)이다.


1
finnw-P3 notify은 P3 (이 예제에서 선택된 스레드)이 대기중인 지점 (즉, while루프 내)에서 진행하게 되므로 조건을 다시 확인해야합니다 . 교착 상태를 유발하지 않는 다른 예가 있지만이 경우 notify교착 상태가없는 코드를 사용 한다고 보장 할 수 없습니다. 사용 notifyAll합니다.
xagyg

4
@marcus 매우 가깝습니다. notifyAll을 사용하면 각 스레드가 한 번에 하나씩 잠금을 다시 획득하지만 한 스레드가 잠금을 다시 획득하고 메소드를 실행 한 다음 종료합니다. 다음 스레드는 잠금을 다시 획득하여 "while"을 확인합니다. (물론 조건에 따라) "대기"로 돌아갑니다. 따라서 올바른 상태로 알릴 때 스레드 하나를 깨 웁니다. notifyAll은 모든 스레드를 깨우고 각 스레드는 한 번에 하나씩 잠금을 다시 획득합니다. "while"의 상태를 확인하고 메소드를 실행하거나 "wait"합니다.
xagyg

1
@ xagyg, 모든 생산자가 저장할 단일 문자가 하나 뿐인 시나리오에 대해 이야기하고 있습니까? 그렇다면 귀하의 예는 정확하지만 흥미로운 IMO는 아닙니다. 추가 단계를 수행하면 동일한 시스템을 교착 상태로 만들 수 있지만 무한한 양의 입력으로 이러한 패턴이 실제로 실제로 사용되는 방식입니다.
eran

3
@codeObserver 당신은 물었다 : "notify (()를 호출하면 여러 대기 스레드가 while () 조건을 동시에 검사하게 될 것입니다.) 예외?. " 여러 스레드가 깨어 지더라도 while 상태를 동시에 확인할 수 없으므로 불가능합니다. 코드 섹션을 다시 입력하고 잠시 동안 다시 확인하려면 잠금을 다시 받아야합니다 (대기 직후). 따라서 한 번에 하나씩.
xagyg

4
@xagyg 좋은 예입니다. 이것은 원래 질문에서 벗어난 주제입니다. 단지 토론을 위해. 교착 상태는 디자인 문제 imo입니다 (잘못되면 정정하십시오). put과 get이 공유하는 하나의 잠금이 있기 때문입니다. 그리고 JVM은 잠금을 해제 한 후 put을 호출하기에 충분하지 않습니다. put은 다른 put을 깨워서 while () 때문에 wait ()로 돌아 가기 때문에 dead lock이 발생합니다. 두 개의 클래스 (및 두 개의 잠금 장치)가 작동합니까? put {synchonized (get)}, get {(synchonized (put)}. 즉, get은 put 만 깨우고 put은 깨우 게됩니다
Jay

43

유용한 차이점 :

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

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


19

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

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

나는 여기서 좋은 설명을 찾았다 . 한마디로 :

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


11

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

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


10

짧은 요약:

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

설명:

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의 위험보다 낮다고 가정합니다 . 오히려 대부분의 스케줄러 구현은 기아를 방지하기 위해 최소한의 노력을 기울입니다.


9

다음은 예입니다. 그것을 실행하십시오. 그런 다음 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) { }
        }
    }
}

8

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

"항목 69 : 대기하고 알리는 동시성 유틸리티 선호"


16
이유 는 소스보다 더 중요합니다.
Pacerier

2
@Pacerier 잘 말했다. 이유를 찾는 데 더 관심이 있습니다. 가능한 한 가지 이유는 객체 클래스에서 대기 및 알림이 암시 적 조건 변수에 기반하기 때문일 수 있습니다. 따라서 표준 생산자와 소비자 사례에서 ..... 생산자와 소비자 모두 동일한 조건에서 기다릴 것이며 xagyg가 그의 답변에서 설명한대로 교착 상태를 초래할 수 있습니다. 따라서 더 나은 접근 방법은 docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/
rahul

6

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

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

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

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

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

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


6

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

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

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

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


그냥 멋진 설명입니다.
royatirek

5

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

원래:

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

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

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

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

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


제 사과, xagyg가 자세히 설명합니다. 문제의 이름은 "lost wakeup"
NickV

@ Abhay Bansal : condition.wait ()가 잠금을 해제하고 깨우는 스레드에 의해 다시 획득된다는 사실이 누락되었다고 생각합니다.
NickV

4

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


4

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


4

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루프 조건 을 확인합니다 .


4

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

notify ()를 사용하든 notifyAll ()을 사용하든, 즉시 다른 스레드가 모니터를 획득하고 실행을 시작한다는 것이 바로 올바른 결과입니다. (일부 스레드가이 객체에 대해 wait ()에서 실제로 차단되었다고 가정하면 관련없는 다른 스레드가 사용 가능한 모든 코어를 흡수하지는 않습니다.) 영향은 나중에 발생합니다.

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

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


매우 명확한 설명. notify () 동작의 결과로 "Missed Signal"/ "Missed Notification"이 발생하여 응용 프로그램 상태의 교착 상태 / 진행 상황이 발생하지 않을 수 있습니다 .P-Producer, C-Consumer P1, P2 및 C2는 C1을 기다리고 있습니다. C1은 notify ()를 호출하고 생산자를위한 것이지만 C2는 깨어날 수 있으므로 P1과 P2는 모두 알림을받지 못하고 더 명시적인 "알림"(notify () 호출)을 기다립니다.
user104309

4

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

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

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

생산자:

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

소비자:

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

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

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

통지로 인한 교착 상태


3

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

첫째, Notify 또는 NotifyAll?

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

두 개의 스레드 A와 B가 동일한 조건 큐의 다른 조건 술어를 대기하고 통지하면 호출되는 스레드는 JVM까지입니다.

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

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

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


3

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

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

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


3

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

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에 의한 선택보다 훨씬 낫습니다. 따라서 생기를 보장합니다.

2

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

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

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

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


1

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

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

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


13
이 경우 notify()정확히 " 첫 번째 스레드 " 가 아닙니다 .
Bhesh Gurung

6
VM이 어떤 것을 선택할지 예측할 수 없습니다. 하나님 만이 아신다.
Sergii Shevchyk 2014 년

누가 최초가 될지에 대한 보장은 없습니다 (공평하지 않음)
Ivan Voroshilin

OS가 보장하는 경우 첫 번째 스레드 만 깨우며 그렇지 않을 가능성이 높습니다. 실제로 어떤 스레드를 깨 울지 결정하기 위해 OS (및 스케줄러)를 연기합니다.
Paul Stelian

1

notify는 대기 상태에있는 하나의 스레드 만 통지하는 반면, all all은 대기 상태에있는 모든 스레드에 대해 모든 통지 된 스레드와 모든 차단 된 스레드가 잠금을받을 수 있으며, 그 중 하나만 잠금을 얻습니다. 대기 상태에있는 사람을 포함하여 다른 모든 사람은 차단 상태에있게됩니다.


1

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

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

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

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


0

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

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

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

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

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


0

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


필자의 경험에 따르면 대기 / 알림 사용은 종종 스레드 ( Runnable구현)가 대기열의 내용을 처리하는 대기열 메커니즘에서 사용 됩니다. 는 wait()큐가 비어있을 때마다 다음 사용됩니다. 그리고 notify()정보가 추가되면 호출됩니다. -> 이러한 경우에는을 호출하는 스레드가 하나 뿐이며 대기 스레드가 하나만 있다는 것을 알고 있으면 wait()조금 어리석은 것처럼 보이지 않습니다 notifyAll().
bvdb

-2

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.