ProgramingTip

왜 wait ()는 항상 루프 내에서 호출됩니까?

bestdevel 2020. 11. 23. 19:46
반응형

왜 wait ()는 항상 루프 내에서 호출됩니까?


wait()루프 내에서 항상 호출해야한다는 것을 읽었습니다 .

while (!condition) { obj.wait(); }

루프없이 잘 작동하는데 왜 그렇습니까?


루프를 반복하지 않는 것이 루프에서 상태를 확인해야합니다. Java는 notify () 호출 또는 올바른 notify () / notifyAll () 호출에만 선언가 깨어날 보장하지 않습니다. 이 특성으로 인해 루프없는 버전은 개발 환경에서 작동하고 설치 환경에서 중단 될 수 있습니다.

예를 들어, 당신은 준비를 기다리고 있습니다.

synchronized (theObjectYouAreWaitingOn) {
   while (!carryOn) {
      theObjectYouAreWaitingOn.wait();
   }
}

사악한 실이 따라옵니다.

theObjectYouAreWaitingOn.notifyAll();

사악한 만들가 carryOn당신을 엉망으로 만들지 거의 할 수 없는 클라이언트를 계속 기다리십시오.

편집 : 더 많은 샘플을 추가했습니다. 대기가 중단 될 수 있습니다. InterruptedException이 발생하고 try-catch에서 대기를 래핑해야 할 수도 있습니다. 비즈니스 요구 사항에 따라 예외를 종료하고 계속 대기 할 수 있습니다.


Object.wait (long milis)에 대한 문서에 답변이 있습니다.

표준은 알림, 중단 또는 시간 초과없이 깨어날 수 있습니다. 실제로는 거의 발생하지 않지만 애플리케이션은 반복가 방지 깨어나야하는 조건을 테스트하고 조건이 계속 대기하여이를 수행해야합니다. 즉, 대기는 항상 다음과 같은 루프에서 발생해야합니다.

 synchronized (obj) {
     while (<condition does not hold>)
         obj.wait(timeout);
     ... // Perform action appropriate to condition
 }

(이 주제에 대한 더 많은 정보는 Doug Lea의 "Concurrent Programming in Java (Second Edition)"(Addison-Wesley, 2000)의 섹션 3.2.3 또는 Joshua Bloch의 "Effective Java Programming Language Guide"(Addison- Wesley, 2001).


왜 wait ()는 항상 루프 내에서 호출됩니까?

while루프가 중요한 주된 이유 는 동일한 경쟁 조건입니다. 확실히 스퓨리어스 웨이크 업은 실제적인 특정 아키텍처에서는 일반적이지만 경합 상태가 while루프의 원인 일 가능성이 훨씬 더 많은 .

예를 들면 :

synchronized (queue) {
    // this needs to be while
    while (queue.isEmpty()) {
       queue.wait();
    }
    queue.remove();
}

위의 코드를 사용하면 2 개의 코드를 사용할 수 있습니다. 생산자 queue가 추가하기 위해 synchronized위해 잠그면 소비자 # 2가 queue. 이 대기열에 항목 추가되고 notify생산자가 호출하면 # 2 대기 대기열에서가 이동되어 queue잠금 이 차단 되지만 이미 잠금이 차단 된 # 1 소비자 뒤 6 있게됩니다 . 이 방법 # 1 소비자는 전화로 먼저 전진하는 것을 remove()얻습니다 queue. 는 IF while루프는 단지 if소비자 # 2 # 1과 통화 후 잠금을 얻을 때, 다음 remove(), 예외가 있기 때문에 발생하는 것 queue입니다. 다른 소비자는 이미 항목을 제거했습니다. 알림 queue받기 때문에 경쟁 조건으로 인해 여전히 비어 있는지 확인해야합니다 .

이것은 잘 문서화되었습니다. 다음은 경쟁 조건을 자세히 설명 하고 샘플 코드가 있는 얼마 전에 만든 웹 페이지 입니다.


조건이 참이되기를 기다리며 한 명 이상일 수 있습니다.

두 명 이상의 경우가 깨어 있으면 (notifyAll) 상태를 다시 확인해야합니다. 일련의 그들 중 하나에 대한 데이터 만있을지라도 모든 것이 계속됩니다.


@Gray의 대답을 얻은 것입니다.

저와 같은 메시지를 위해 그것을 바꾸고 제가 틀 렸습니다.

소비자 동기화 블록 : :

synchronized (queue) {
    // this needs to be while
    while (queue.isEmpty()) {
       queue.wait();
    }
    queue.remove();
}

생산자 동기화 블록 : :

synchronized(queue) {
 // producer produces inside the queue
    queue.notify();
}

다음이 주어진 순서대로 발생 가정합니다.

1) 소비자 # 2는 컨슈머 synchronized블록에있는 가 비어 있기 때문에 기다리고 있습니다.

2) 이제 생산자가 잠금을 하고 남아 있음 queue알리고 ()를 호출합니다.

이제 처음으로 queue잠금이 synchronized블록에 있긴 하지만 사용 가능한 소비자 # 1을 사용할 수 있습니다.

또는

소비자 # 2를 선택하여 언어 수 있습니다.

3) 소비자 # 1이 실행을 계속하도록 선택합니다. 조건을 확인하면 참이고, remove()나타납니다.

4) consumer # 2가 실행을 중지 한 지점 ( wait()메소드 다음 줄 )에서 진행 중입니다. 'while'조건이 없으면 ( if조건 대신 ) remove()예외 / 예기치 않은 동작이 발생할 수있는 호출 만 진행됩니다 .


대기 및 알림은 [조건 변수] ( http://en.wikipedia.org/wiki/Monitor_(synchronization)#Blocking_condition_variables) 를 구현하는 데 사용 되므로 대기중인 특정 조건자가 이전에 참인지 확인해야합니다. 계속.


대기 / 알림 메커니즘을 사용할 때 안전과 활성 모두 문제가됩니다. 안전 속성을 사용하려면 모든 개체가 다중 스레드 환경에서 일관된 상태를 유지해야합니다. 활성 속성은 모든 작업 또는 메서드 호출이 중단없이 완료 될 때까지 실행되어야합니다.

활성 상태를 보장하기 위해 프로그램은 wait () 메서드를 호출하기 전에 while 루프 조건을 테스트해야합니다. 이 초기 테스트는 다른 스레드가 이미 조건 술어를 충족하고 알림을 보냈는지 여부를 확인합니다. 알림을 보낸 후 wait () 메서드를 호출하면 무기한 차단이 발생합니다.

안전을 보장하기 위해 프로그램은 wait () 메서드에서 반환 한 후 while 루프 조건을 테스트해야합니다. wait ()는 알림을받을 때까지 무기한 차단하기위한 것이지만 다음과 같은 취약점을 방지하기 위해 여전히 루프 내에 넣어야합니다.

중간 스레드 : 세 번째 스레드는 알림이 전송되고 수신 스레드가 실행을 재개하는 간격 동안 공유 객체에 대한 잠금을 획득 할 수 있습니다. 이 세 번째 스레드는 개체의 상태를 변경하여 일관성을 유지할 수 있습니다. 이것은 TOCTOU (time-of-use, time-of-use) 경쟁 조건입니다.

악성 알림 : 조건 술어가 거짓 인 경우 무작위 또는 악성 알림을 수신 할 수 있습니다. 이러한 알림은 wait () 메서드를 취소합니다.

잘못 전달 된 알림 : notifyAll () 신호를받은 후 스레드가 실행되는 순서는 지정되지 않습니다. 결과적으로 관련없는 스레드가 실행을 시작하고 해당 조건 술어가 충족되었음을 발견 할 수 있습니다. 따라서 휴면 상태를 유지해야하더라도 실행을 재개 할 수 있습니다.

스퓨리어스 웨이크 업 : 특정 JVM (Java Virtual Machine) 구현은 알림 없이도 대기중인 스레드가 깨어나는 스퓨리어스 웨이크 업에 취약합니다.

이러한 이유로 프로그램은 wait () 메서드가 반환 된 후 조건 술어를 확인해야합니다. while 루프는 wait ()를 호출하기 전과 후에 조건 술어를 확인하는 가장 좋은 선택입니다.

마찬가지로 Condition 인터페이스의 await () 메서드도 루프 내에서 호출되어야합니다. Java API에 따르면 Interface Condition

조건을 기다릴 때 일반적으로 기본 플랫폼 의미 체계에 대한 양보로 "가짜 깨우기"가 발생할 수 있습니다. 대기중인 상태 술어를 테스트하는 조건은 항상 루프에서 대기해야하므로 대부분의 애플리케이션 프로그램에 거의 영향을 미치지 않습니다. 구현은 스퓨리어스 웨이크 업의 가능성을 자유롭게 제거 할 수 있지만 응용 프로그램 프로그래머는 항상 발생할 수 있다고 가정하고 항상 루프에서 대기하는 것이 좋습니다.

새 코드는 wait / notify 메커니즘 대신 java.util.concurrent.locks 동시성 유틸리티를 사용해야합니다. 그러나이 규칙의 다른 요구 사항을 준수하는 레거시 코드는 대기 / 알림 메커니즘에 의존하도록 허용됩니다.

비준수 코드 예제이 비준수 코드 예제 는 기존 if 블록 내에서 wait () 메서드를 호출하고 알림을받은 후 사후 조건을 확인하지 못합니다. 알림이 우발적이거나 악의적 인 경우 스레드가 조기에 깨어날 수 있습니다.

synchronized (object) {
  if (<condition does not hold>) {
    object.wait();
  }
  // Proceed when condition holds
}

준수 솔루션 이 준수 솔루션은 while 루프 내에서 wait () 메서드를 호출하여 wait () 호출 전후의 조건을 모두 확인합니다.

synchronized (object) {
  while (<condition does not hold>) {
    object.wait();
  }
  // Proceed when condition holds
}

java.util.concurrent.locks.Condition.await () 메소드의 호출도 유사한 루프로 묶어야합니다.


귀하의 질문에서 :

루프 내에서 항상 wait ()를 호출해야한다는 것을 읽었습니다.

wait ()는 일반적으로 notify () 또는 notifyAll ()이 호출 될 때까지 대기하지만 매우 드물게 스퓨리어스 웨이크 업으로 인해 대기중인 스레드가 깨어날 가능성이 있습니다. 이 경우 대기 스레드는 notify () 또는 notifyAll () 호출없이 재개됩니다.

본질적으로 스레드는 명백한 이유없이 재개됩니다.

이러한 원격 가능성 때문에 Oracle은 스레드가 대기중인 상태를 확인하는 루프 내에서 wait () 호출을 수행 할 것을 권장합니다.


사람들이하는 것을 볼 수있는 세 가지 :

  • 아무것도 확인하지 않고 대기 사용 (BROKEN)

  • 조건과 함께 대기를 사용하고 먼저 if 확인 (BROKEN)을 사용합니다.

  • 루프 테스트가 조건 (NOT BROKEN)을 확인하는 루프에서 대기를 사용합니다.

작업 대기 및 알림 방법에 대한 이러한 세부 정보를 인식하지 않으면 사람들이 잘못된 접근 방식을 선택하게됩니다.

  • 하나는 스레드가 대기하기 전에 발생한 알림을 기억하지 않는다는 것입니다. notify 및 notifyAll 메소드는 스레드가 운이 없을 때 대기하지 않는 경우 이미 대기중인 스레드에만 영향을줍니다.

  • 다른 하나는 스레드가 대기를 시작하면 잠금을 해제한다는 것입니다. 알림을 받으면 잠금을 다시 획득하고 중단 된 지점에서 계속됩니다. 잠금을 해제하면 스레드가 일단 깨어 나면 현재 상태에 대해 아무것도 알지 하므로 그 이후로 다른 스레드가 변경되었을 수 있습니다. 스레드가 대기를 시작하기 전에 이루어진 검사는 현재 상태에 대해 알려주지 않습니다.

따라서 검사가없는 첫 번째 경우는 경쟁 조건에 취약한 코드를 만듭니다. 한 스레드가 다른 스레드보다 충분한 헤드 시작을 가지고 있으면 우연히 작동 할 수 있습니다. 또는 스레드가 영원히 기다리고있을 수 있습니다. 시간 초과에 뿌려지면 때때로 원하는 작업을 수행하지 않는 느린 코드로 끝납니다.

알림 자체와 별도로 확인하는 조건을 추가하면 이러한 경쟁 조건으로부터 코드를 보호하고 스레드가 적절한 시간에 대기하지 않았더라도 코드가 상태를 알 수있는 방법을 제공합니다.

두 번째 경우는 if-checks로 스레드가 2 개만있는 경우 작동 할 가능성이 높습니다. 그것은 사물이 들어갈 수있는 상태의 수에 제한을 두며 잘못된 가정을 할 때 그렇게 심하게 태워지지 않습니다. 이것은 많은 장난감 예제 코드 연습의 상황입니다. 그 결과 사람들은 이해하지 못한다고 생각하고 떠납니다.

Protip : 실제 코드에는 두 개 이상의 스레드가 있습니다.

루프를 사용하면 잠금을 다시 획득 한 후 상태를 다시 확인할 수 있으므로 오래된 상태가 아닌 현재 상태를 기준으로 앞으로 나아갈 수 있습니다.

참고 URL : https://stackoverflow.com/questions/1038007/why-should-wait-always-be-called-inside-a-loop

반응형