2013-04-26 8 views
6

Ich habe ein Java-Thread so etwas wie dies funktioniert:Java-Thread aufwacht spontan

while (running) { 
    synchronized (lock) { 
     if (nextVal == null) { 
      try { 
       lock.wait(); 
      } catch (InterruptedException ie) { 
       continue; 
      } 
     } 

     val = nextVal; 
     nextVal = null; 
    } 
    ...do stuff with 'val'... 
} 

An anderer Stelle habe ich den Wert wie folgt aus:

if (val == null) { 
    LOG.error("null value"); 
} else { 
    synchronized (lock) { 
     nextVal = newVal; 
     lock.notify(); 
    } 
} 

Gelegentlich (buchstäblich einmal alle paar Millionen mal) nextVal wird auf null gesetzt. Ich habe in Log-Meldungen geworfen, und ich kann sehen, dass die Reihenfolge der Ausführung wie folgt aussieht:

  1. Thread1 NEXTVAL zu newVal setzt
  2. Thread1 lock.notify() aufruft
  3. Thread2 von Schloss aufwacht. (warten)
  4. Thread2 val zu NEXTVAL setzt
  5. Thread2 NEXTVAL setzt
  6. auf null Thread2 Sachen tut mit val
  7. Thread2 lock.wait() aufruft
  8. Thread2 von lock.wait aufwacht()
    • kein anderer Thread aufgerufen lock.notify() und Thread2 nicht unterbrochen worden
  9. Thread2 setzt val zu NEXTVAL (die null ist)
  10. hat
  11. usw.

ich explizit überprüft haben und die Sperre wird ein zweites Mal aufwacht, ist es nicht unterbrochen wird.

Mache ich hier etwas falsch?

+0

+1 für eine gut geschriebene Erklärung, aber -1 für das Lesen der Javadoc nicht sorgfältiger :-) –

+0

IIRC, es gibt keine Garantie, dass ein Thread nicht von einer Wartezeit aufgeweckt wird, während die Wartebedingung noch nicht erfüllt ist. Sie müssen die Bedingung immer überprüfen und die Wartezeit erneut ausführen, wenn sie nicht erfüllt ist. –

+0

(Der Grund dafür hat mit der Komplexität der Implementierung der "Weck" -Logik zu tun. Das garantieren, dass es nie ein unechtes Wecken gibt, ist ohne eine teure Synchronisation fast unmöglich.) –

Antwort

3

Yup, Thread s spontan aufwachen. Das dies ist ausdrücklich in the Javadoc angegeben:

„Ein Thread auch aufwachen kann, ohne benachrichtigt, unterbrochen wird oder eine Zeitüberschreitung, eine sogenannte falsche Wakeup.“

Sie müssen wait in einer Schleife. Dies wird auch explizit in der javadoc erwähnt:

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

In Ihrem Fall:

while (running) { 
    synchronized (lock) { 
     while (nextVal == null) { 
      try { 
       lock.wait(); 
      } catch (InterruptedException ie) { 
       //oh well 
      } 
     } 

     val = nextVal; 
     nextVal = null; 
    } 
    ...do stuff with 'val'... 
} 
+0

Ich sollte beachten, dass die 'while (nextVal == null) {' Lösung für mich nicht funktioniert, weil ich das Detail weggelassen, dass dieser Thread gestoppt wird über eine Methode, die: synchronisiert (lock) { running = true ; lock.notify(); } so kann ich auf 'nextVal == null' nicht drehen.Stattdessen fügte ich hinzu: if (nextVal == null) fortfahren; vor dem 'val = nextVal', um sicherzustellen, dass 'running' bei jeder Fahrt durch die Schleife überprüft wird. – dglo

+1

Entfernen Sie das ganze, machen Sie es zu einem 'while (true)'. Überprüfen Sie die 'running' Flagge irgendwo anders und' return'. Sie können sogar eine Logik in den Interrupt einfügen, dann können Sie den 'Thread' unterbrechen, um zu stoppen. –

+0

Ja, Interrupts ist der richtige Weg, um einen laufenden Thread zu stoppen. Die Verwendung eines separaten "booleschen" Flag-Ansatzes ist voller Fallstricke. Ich hoffe, 'running' wird hier als' volatile' gekennzeichnet! –

1

Unechte Wake ups sind relativ häufig und daher ist es immer ratsam, wait() unter einer Bedingung in einer Schleife.

Ihren Code wie folgt ändern:

while (nextVal == null) { 
    try { 
     lock.wait(); 
    } catch (InterruptedException ignored) { 
    } 
} 

Spezifisch für den Code, den Sie freigegeben haben: while helfen Sie auch den unnötigen Aufwand für die Freigabe und Wieder Erwerb die gleiche Sperre zu vermeiden, wenn Ihr Code continue;

Hits

Referenzen:
http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Object.html#wait()

Vom public final void wait() Dokumentation:
... Interrupts und unechte Wakeups sind möglich, und dieses Verfahren sollte immer in einer Schleife verwendet werden:

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

Dies ist eine falsche Wakeup sein könnte, aber das ist nicht die einzige mögliche Ursache . Es ist definitiv ein Problem mit Ihrer Logik. Sie müssen die Wartezeit in eine Schleife setzen, die für die Bedingung erneut testet.

Wenn ein Thread vom Warten aufwacht, hat er das Schloss nicht mehr. Es freigegeben das Schloss, als es begann zu warten, muss es die Sperre erneut erwerben, bevor es fortfahren kann. Der gerade geweckte Thread kann aufgrund der Thread-Affinität in der Regel der nächste sein (was wahrscheinlich der Grund ist, warum dein Code die meiste Zeit funktioniert), aber es besteht immer noch die Möglichkeit, dass dies nicht der Fall ist. ein anderer Thread kann kommen und das Schloss einhaken, sein Ding machen und den nextVal Null lassen, bevor der aufgeweckte Thread das Schloss übernehmen kann. Das bedeutet, dass der Test für null, dass der vor dem Warten erstellte Thread nicht mehr relevant ist. Sie müssen zurückgehen und erneut testen, sobald Sie das Schloss haben.

Ändern Sie den Code, um eine Schleife zu verwenden, wie:

synchronized(lock) { 
    while (nextVal == null) { 
     lock.wait(); 
    } 
    ... 

diese Weise wird der Test durchgeführt wird, während der Thread die Sperre hat, und was geschieht in dem Block unter der while-Schleife bestimmte NEXTVAL wirklich isn sein kann 't null.

Verwandte Themen