분명히, 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 개의 스레드가 영구적으로 일시 중단되었습니다!
해결책 : 교체 notify와 notifyAll생산자 / 소비자 코드 (위)이다.