wait ()가 항상 동기화 된 블록에 있어야하는 이유


257

우리는 모두를 호출하기 Object.wait()위해이 호출은 반드시 동기화 된 블록에 있어야하며 그렇지 IllegalMonitorStateException않으면가 발생 한다는 것을 알고 있습니다 . 그러나이 제한을 만드는 이유는 무엇입니까? 나는 그것이 wait()모니터 를 릴리스 한다는 것을 알고 있지만 왜 특정 블록을 동기화하여 모니터를 명시 적으로 얻은 다음 호출하여 모니터를 해제해야 wait()합니까?

wait()동기화 된 블록 외부 에서 호출 하여 의미를 유지하면서 호출자 스레드를 일시 중단 할 수있는 경우 잠재적 손상은 무엇입니까 ?

답변:


232

A는 wait()단지 또한이있을 때 의미가 notify()이 스레드 사이의 통신에 대해 항상 그래서, 작업에 필요 동기화가 제대로. 이것은 암시 적이어야한다고 주장 할 수 있지만 다음과 같은 이유로 실제로 도움이되지는 않습니다.

의미 상, 당신은 결코 wait(). 만족 시키려면 어떤 상태가 필요하며, 그렇지 않은 경우 기다릴 수 있습니다. 그래서 당신이 정말로하는 것은

if(!condition){
    wait();
}

그러나 조건은 별도의 스레드로 설정 되므로이 작업을 올바르게 수행하려면 동기화가 필요합니다.

스레드가 종료되었다고해서 찾고있는 조건이 사실을 의미하지는 않습니다.

  • 스퓨리어스 웨이크 업 (스레드가 알림을받지 않고 대기에서 깨어날 수 있음을 의미) 또는

  • 조건이 설정 될 수 있지만 대기중인 스레드가 깨어나고 모니터를 다시 가져올 때까지 세 번째 스레드가 조건을 다시 거짓으로 만듭니다.

이러한 경우를 처리하기 위해 실제로 필요한 것은 항상 약간의 변형입니다.

synchronized(lock){
    while(!condition){
        lock.wait();
    }
}

더 나은 방법은 동기화 프리미티브를 엉망으로 만들지 말고 java.util.concurrent패키지에 제공된 추상화를 사용하는 것 입니다.


3
본질적으로 동일한 것을 말하는 여기에도 자세한 논의가 있습니다. coding.derkeiler.com/Archive/Java/comp.lang.java.programmer/…

1
btw, 중단 된 플래그를 무시하지 않으면 루프도 점검해야 Thread.interrupted()합니다.
bestsss

2
while (! condition) {synchronized (this) {wait ();}} : 동기화 된 블록에서 wait ()가 올바르게 호출 되더라도 조건을 확인하는 것과 대기하는 것 사이에 경쟁이 여전히 있음을 의미합니다. Java에서 구현 된 방식으로 인해이 제한 뒤에 다른 이유가 있습니까?
shrini1000

9
또 다른 불쾌한 시나리오 : condition이 false이면 wait ()로 이동 한 다음 다른 스레드가 조건을 변경하고 notify ()를 호출합니다. 아직 wait ()에 있지 않기 때문에이 notify ()를 놓치게됩니다. 다시 말해, 변경 및 알림뿐만 아니라 테스트 및 대기는 atomic 이어야합니다 .

1
@Nullpointer : 원자 적으로 작성 될 수있는 유형 (예 : if 절에서 직접 사용하여 boolean과 같은 유형)이고 다른 공유 데이터와의 상호 의존성이 없으면 휘발성으로 선언 할 수 있습니다. 그러나 업데이트가 다른 스레드에 즉시 표시되도록하려면 동기화 또는 동기화가 필요합니다.
Michael Borgwardt 2016 년

282

wait()동기화 된 블록 외부 에서 호출 하여 의미를 유지하면서 호출자 스레드를 일시 중단 할 수있는 경우 잠재적 손상은 무엇입니까 ?

구체적인 예제wait() 를 통해 동기화 된 블록 외부에서 호출 할 수있는 경우 어떤 문제가 발생하는지 설명해 보겠습니다 .

차단 대기열을 구현한다고 가정합니다 (API에 이미 대기열이 있음을 알고 있습니다 :)

첫 번째 시도 (동기화 없음)는 아래 줄을 따라 표시 될 수 있습니다.

class BlockingQueue {
    Queue<String> buffer = new LinkedList<String>();

    public void give(String data) {
        buffer.add(data);
        notify();                   // Since someone may be waiting in take!
    }

    public String take() throws InterruptedException {
        while (buffer.isEmpty())    // don't use "if" due to spurious wakeups.
            wait();
        return buffer.remove();
    }
}

이것은 잠재적으로 일어날 수있는 일입니다.

  1. 소비자 스레드가 호출 take()하고 buffer.isEmpty().

  2. 소비자 스레드가 계속 호출하기 전에 wait()생산자 스레드가 와서 전체를 호출합니다 give().buffer.add(data); notify();

  3. 컨슈머 스레드는 이제 호출합니다 wait()( 방금 호출 한 것을 놓치게됩니다notify() ).

  4. 운이 좋지 않으면 give()소비자 스레드가 깨어나지 않아 생산자 스레드가 더 많이 생산 하지 않으며 교착 상태가 발생합니다.

당신이 문제를 이해하면,이 솔루션은 분명하다 : 사용 synchronized확인하는 notify사이에 호출되지 않습니다 isEmptywait.

세부 사항으로 이동하지 않고이 동기화 문제는 보편적입니다. Michael Borgwardt가 지적했듯이 대기 / 알림은 스레드 간의 통신에 관한 것이므로 항상 위에서 설명한 것과 유사한 경쟁 조건으로 끝납니다. 이것이 "동기화 된 내부 대기"규칙이 적용되는 이유입니다.


@Willie가 게시 한 링크 의 단락은 다음과 같이 요약합니다.

웨이터와 알리미가 술어 상태에 대해 동의한다는 절대적인 보증이 필요합니다. 웨이터는 잠들기 전에 어느 시점에서 술어의 상태를 약간 점검하지만, 잠자기 상태에있을 때 술어의 정확성에 달려 있습니다. 이 두 이벤트 사이에 취약점이있어 프로그램을 중단시킬 수 있습니다.

생산자와 소비자가 동의해야하는 조건은 위의 예에 buffer.isEmpty()있습니다. 대기 및 알림이 synchronized블록 단위 로 수행되도록하여 계약이 해결됩니다 .


이 게시물은 다음 기사로 다시 작성되었습니다. Java : 동기화 된 블록에서 대기를 호출해야하는 이유


또한 wait ()가 완료 된 직후 조건에 대한 변경 사항이 표시되도록하기 위해 추측합니다. 그렇지 않은 경우에도 notify ()가 이미 호출 된 이후 교착 상태입니다.
Surya Wijaya Madjid

흥미롭지 만, 단지 동기화 된 호출은 실제로 wait () 및 notify ()의 "신뢰할 수없는"특성으로 인해 이러한 문제를 해결하지는 않습니다. 자세한 내용은 여기 읽기 : stackoverflow.com/questions/21439355/...를 . 동기화가 필요한 이유는 하드웨어 아키텍처 내에 있습니다 (아래 답변 참조).
Marcus

하지만 return buffer.remove();while 블록 이후에 추가 wait();하면 작동합니까?
BobJiang

@BobJiang, 아니오, 누군가에게 전화를주는 것 이외의 이유로 스레드를 깨울 수 있습니다. 즉, wait리턴 후에도 버퍼가 비어있을 수 있습니다 .
aioobe

나는 단지이 Thread.currentThread().wait();main기능에 대한 시도 - 캐치에 의해 둘러싸인 InterruptedException. synchronized블록이 없으면 동일한 예외가 발생 IllegalMonitorStateException합니다. 현재 불법 상태에 도달하는 이유는 무엇입니까? synchronized그래도 블록 내부에서 작동합니다 .
Shashwat

12

@Rollerball이 맞습니다. 는 wait()스레드가이 때 발생하는 몇 가지 조건을 기다릴 수 그래서,라고 wait()호출이 스레드의 잠금을 포기하도록 강요되어 발생합니다.
무언가를 포기하려면 먼저 소유해야합니다. 스레드는 먼저 잠금을 소유해야합니다. 따라서 synchronized메소드 / 블록 내에서 호출해야합니다 .

예, synchronized방법 / 차단 내 조건을 확인하지 않은 경우 잠재적 손상 / 불일치에 관한 위의 모든 답변에 동의합니다 . 그러나 @ shrini1000이 지적했듯이 wait()동기화 된 블록 내에서 호출 하면이 불일치가 발생하는 것을 막을 수 없습니다.

여기 좋은 읽을 거리가 있습니다 ..


5
@Popeye '적절하게'올바르게 설명하십시오. 귀하의 의견은 아무 소용이 없습니다.
Lorne의 후작

4

이전 에 동기화 하지 않으면 발생할 수있는 문제 wait()는 다음과 같습니다.

  1. 첫 번째 스레드가 들어가서 makeChangeOnX()while 조건을 확인하면 true( x.metCondition()return false, means x.conditionis false) 내부에 들어갑니다. 그리고 바로 전에 wait()방법, 다른 스레드로 이동 setConditionToTrue()하고 설정합니다 x.conditiontruenotifyAll().
  2. 그런 다음에 만 첫 번째 스레드가 그의 wait()방법에 들어갑니다 ( notifyAll()몇 분 전에 발생한 것에 영향을받지 않음 ). 이 경우 첫 번째 스레드는 다른 스레드가 수행 할 때까지 대기 setConditionToTrue()하지만 다시는 발생하지 않을 수 있습니다.

그러나 synchronized객체 상태를 변경하는 메소드 앞에 놓으면 이런 일이 발생하지 않습니다.

class A {

    private Object X;

    makeChangeOnX(){
        while (! x.getCondition()){
            wait();
            }
        // Do the change
    }

    setConditionToTrue(){
        x.condition = true; 
        notifyAll();

    }
    setConditionToFalse(){
        x.condition = false;
        notifyAll();
    }
    bool getCondition(){
        return x.condition;
    }
}

2

우리는 wait (), notify () 및 notifyAll () 메소드가 스레드 간 통신에 사용된다는 것을 알고 있습니다. 누락 된 신호와 가짜 웨이크 업 문제를 제거하기 위해 대기 스레드는 항상 일부 조건에서 대기합니다. 예-

boolean wasNotified = false;
while(!wasNotified) {
    wait();
}

그런 다음 스레드 세트 알리기 wasNotified 변수를 true로 설정하고 알립니다.

모든 스레드에는 로컬 캐시가 있으므로 모든 변경 사항이 먼저 기록 된 다음 점차 메인 메모리로 승격됩니다.

이러한 메소드가 동기화 된 블록 내에서 호출되지 않은 경우 wasNotified 변수는 기본 메모리로 플러시되지 않고 스레드의 로컬 캐시에있을 수 있으므로 대기 스레드는 스레드를 통지하여 재설정되었지만 신호를 계속 대기합니다.

이러한 유형의 문제를 해결하기 위해이 메소드는 항상 동기화 된 블록 내에서 호출되어 동기화 된 블록이 시작되면 모든 것이 주 메모리에서 읽히고 동기화 된 블록을 종료하기 전에 주 메모리로 플러시됩니다.

synchronized(monitor) {
    boolean wasNotified = false;
    while(!wasNotified) {
        wait();
    }
}

고마워, 명확하게 바랍니다.


1

이것은 기본적으로 하드웨어 아키텍처 (예 : RAM캐시 )와 관련이 있습니다.

사용하지 않는 경우 synchronized와 함께 wait()하거나 notify(), 다른 스레드 대신 입력 할 수있는 모니터를 기다리는 같은 블록을 입력합니다. 또한, 예를 들어 동기화 된 블록없이 어레이에 액세스 할 때 다른 스레드가 변경 사항을 보지 못할 수 있습니다. 실제로 다른 스레드 x 레벨 캐시에 어레이의 복사본이 이미 있을 때 변경 사항 을 보지 못합니다 스레드 처리 CPU 코어의 1 차 / 2 차 / 3 차 캐시라고도 함).

그러나 동기화 된 블록은 메달의 한 면일뿐입니다. 실제로 동기화되지 않은 컨텍스트에서 동기화 된 컨텍스트 내 개체에 액세스하는 경우 개체는 자체 복제본을 보유하므로 동기화 된 블록 내에서도 동기화되지 않습니다. 캐시의 객체. 여기이 문제에 대해 쓴 : https://stackoverflow.com/a/21462631잠금이 아닌 최종 객체를 보유 할 때, 또 다른 스레드에 의해 개체의 참조를 변경할 수 있습니다?

또한, x 수준 캐시는 재현 할 수없는 대부분의 런타임 오류를 담당합니다. 개발자는 일반적으로 CPU의 작동 방식이나 메모리 계층이 응용 프로그램 실행에 미치는 영향과 같은 하위 수준 학습을 배우지 않기 때문입니다. http://en.wikipedia.org/wiki/Memory_hierarchy

프로그래밍 클래스가 메모리 계층 구조와 CPU 아키텍처로 시작하지 않는 이유는 여전히 수수께끼입니다. "Hello world"는 여기서 도움이되지 않습니다. ;)



흠 .. 잘 모르겠다. 캐싱이 대기 및 알림 내부에 동기화를 넣는 유일한 이유 인 경우 동기화가 대기 / 알림 구현에 포함되지 않는 이유는 무엇입니까?
aioobe

좋은 질문은 기다림 / 알림이 동기화 된 메소드 일 수 있기 때문입니다 ... Sun의 이전 Java 개발자가 답을 알고 있습니까? 위의 링크를 살펴 보거나 도움이 될 수도 있습니다. docs.oracle.com/javase/specs/jls/se7/html/jls-17.html
Marcus

그 이유는 다음과 같습니다. Java 초기에는 이러한 멀티 스레딩 작업을 수행하기 전에 동기화를 호출하지 않을 때 컴파일 오류가 없었습니다. 대신 런타임 오류 만있었습니다 (예 : coderanch.com/t/239491/java-programmer-SCJP/certification/… ). 아마도 @SUN은 프로그래머가 이러한 오류를 겪을 때 연락을 받고 서버를 더 많이 팔 수 있다고 생각했을 것입니다. 언제 바뀌 었습니까? 아마 Java 5.0이나 6.0이지만 실제로 솔직히 기억 나지 않습니다.
Marcus

TBH 나는 당신의 두 번째 문장이 이해가되지 않습니다) 답변 1 몇 가지 문제를 참조하십시오 그것은 중요하지 않습니다 하는 스레드가 잠금을 가지고 객체. 두 스레드가 동기화하는 개체에 관계없이 모든 변경 사항이 표시됩니다. 2) 다른 스레드가 " 변경 되지 않습니다" 라고 말합니다 . 이것은해야 "하지 않을 수 있습니다" . 3) 왜 당신이 1 / 2/3 레벨 캐시를 가져 오는지 모르겠습니다 ... 여기서 중요한 것은 Java 메모리 모델이 말하고 JLS에 지정되어 있습니다. 하드웨어 아키텍처를 이해하는 데 도움이 될 수 있지만 JLS는 그것이 무엇을 말한다, 그것은 엄격하게이 상황에 무관 말하기된다.
aioobe

0

자바 오라클 튜토리얼 에서 직접 :

스레드가 d.wait를 호출 할 때 d에 대한 고유 잠금을 소유해야합니다. 그렇지 않으면 오류가 발생합니다. 동기화 된 메소드 내에서 대기를 호출하면 본질적 잠금을 얻는 간단한 방법입니다.


필자가 만든 질문에서 질문 작성자가 튜토리얼에서 인용 한 내용을 명확하게 이해하고있는 것 같지 않으며, 내 대답은 "왜"를 설명합니다.
Rollerball

0

객체 t에서 notify ()를 호출하면 java는 특정 t.wait () 메소드에 알립니다. 그러나 Java는 어떻게 특정 대기 메소드를 검색하고 통지합니까?

java는 객체 t에 의해 잠긴 동기화 된 코드 블록 만 조사합니다. java는 특정 t.wait ()를 알리기 위해 전체 코드를 검색 할 수 없습니다.


0

문서에 따라 :

현재 스레드는이 객체의 모니터를 소유해야합니다. 스레드는이 모니터의 소유권을 해제합니다.

wait()방법은 단순히 객체에 대한 잠금을 해제한다는 것을 의미합니다. 따라서 객체는 동기화 된 블록 / 방법 내에서만 잠 깁니다. 스레드가 동기화 블록 외부에 있으면 잠기지 않았 음을 의미하고, 잠기지 않으면 객체에서 무엇을 놓을까요?


0

모니터링 개체 (동기화 블록에서 사용되는 개체) 에서 스레드 대기 , 단일 스레드의 전체 경로에는 n 개의 모니터링 개체가있을 수 있습니다. 스레드가 동기화 블록 외부에서 대기하는 경우 모니터링 오브젝트가없고 다른 스레드가 모니터링 오브젝트에 대한 액세스를 통지하므로 동기화 블록 외부의 스레드가 알림을받는 방법을 알 수 있습니다. 또한 wait (), notify () 및 notifyAll ()이 스레드 클래스가 아닌 객체 클래스에있는 이유 중 하나입니다.

기본적으로 모니터링 개체는 모든 스레드에 대한 공통 리소스이며 모니터링 개체는 동기화 블록에서만 사용할 수 있습니다.

class A {
   int a = 0;
  //something......
  public void add() {
   synchronization(this) {
      //this is your monitoring object and thread has to wait to gain lock on **this**
       }
  }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.