분명히, 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이 잠금을 획득합니다. while
C1이 루프 검사 (guard)를 수행하고 존재하지 않는 요소를 버퍼에서 제거 할 수 없기 때문에 루프 가있는 것이 무엇인지 추측 하십시오 (C2는 이미 그것을 얻었습니다!). 우리는이하지 않은 경우 while
, 우리는을 얻을 것 IndexArrayOutOfBoundsException
C1 버퍼에서 첫 번째 요소를 제거하려고로!
지금,
자, 이제 우리는 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
, 이탈 방법
더 - notify
P2 깨어나는
-하지만, C2의 입장에서 P2 블록 있도록 (P2 잠금을 재 획득한다) 수 P2 전에있어서 진입 put
방법
- C2 대기 루프를 검사하고 버퍼에 더 이상 문자가 없으므로 대기합니다
. C3은 C2 이후에 메소드에 들어가지만 P2 전에 대기 루프를 검사하고 버퍼에 더 이상 문자가 없으므로 대기합니다.
6 단계 :
-지금 : P3, C2 및 C3이 대기 중입니다!
-마지막으로 P2는 잠금을 획득하고 문자를 버퍼에 넣고 알림을 호출하고 메소드를 종료합니다.
7 단계 :
-P2의 알림이 P3을 깨 웁니다 (스레드가 깨어날 수 있음을 기억하십시오)
-P3는 대기 루프 조건을 확인합니다. 버퍼에 이미 문자가 있으므로 대기합니다.
-더 이상 알림을 호출 할 스레드가없고 3 개의 스레드가 영구적으로 일시 중단되었습니다!
해결책 : 교체 notify
와 notifyAll
생산자 / 소비자 코드 (위)이다.