Tue “nichts” während “Bedingung”

Lesezeit: 5 Minuten

Benutzer-Avatar
Erik Vesteraas

Beim Durchsuchen des Codes für die Java 8-Version von ForkJoinPool (die einige interessante Änderungen gegenüber Java 7 aufweist) bin ich auf dieses Konstrukt gestoßen (hier):

do {} while (!blocker.isReleasable() &&
             !blocker.block());

Ich kämpfe damit, warum Sie es so schreiben würden, anstatt nur

while (!blocker.isReleasable() &&
       !blocker.block());

Ist es nur eine Wahl der Semantik/Lesbarkeit, da Sie das erste Konstrukt lesen könnten als do "nothing" while "conditions"? Oder übersehe ich noch einen Zusatznutzen?

Benutzer-Avatar
MicSim

Wenn Sie die Kommentare oben in der Datei lesen, gibt es direkt unter der Klassendeklaration einen Abschnitt, der die Verwendung dieses Konstrukts erklärt:

Style notes

===========

[...]

There are several occurrences of the unusual "do {} while
(!cas...)"  which is the simplest way to force an update of a
CAS'ed variable. There are also other coding oddities (including
several unnecessary-looking hoisted null checks) that help
some methods perform reasonably even when interpreted (not
compiled).

  • Da ich selbst eine Erinnerung brauchte: CAS ist en.wikipedia.org/wiki/Compare-and-Swap

    – Erik Vesteraas

    7. Juli 2014 um 12:02 Uhr

  • Sie haben vielleicht erklärt, wie das Konstrukt hilft, “eine Aktualisierung einer CAS-Variablen zu erzwingen”.

    – Honza Zidek

    7. Juli 2014 um 19:16 Uhr

  • Aber warum verursacht es eine Aktualisierung einer CAS-Variablen im Gegensatz zu der while Ausführung?

    – Benjamin Grünbaum

    8. Juli 2014 um 5:56 Uhr

  • Es sagt nicht, ob do {} while (condition) vs. while (condition) do ; macht keinen unterschied. Wenn es einen Unterschied macht, dann ist dies (a) ein schrecklich schlechter Kommentar, (b) ich halte es für a Insekt den Kommentar nicht bei der Aussage selbst zu haben, und (c) die Tatsache, dass es einen Unterschied macht, ist ein großer WTF in der Sprache.

    – gnasher729

    8. Juli 2014 um 9:16 Uhr

  • Das klingt nach tiefer Magie, wo sich der Umfang ändert do {} while() vermeidet eine JIT-Optimierung, die die while() Verwendet. Die While-Anweisungen sind semantisch äquivalent, sodass der Unterschied in der Implementierung der JVM liegen muss.

    – Michael Shopsin

    9. Juli 2014 um 16:32 Uhr


Benutzer-Avatar
Erik Vesteraas

ForkJoinPool macht ausgiebigen Gebrauch compareAndSwap... aus sun.misc.Unsafe und die meisten Vorkommen von do {} while (...) in ForkJoinPool kann – wie in anderen Antworten erwähnt – durch diesen Kommentar unter der Überschrift Stilhinweise erklärt werden:

* There are several occurrences of the unusual "do {} while
* (!cas...)"  which is the simplest way to force an update of a
* CAS'ed variable. 

Die Wahl, Write a zu verwenden while-Schleife mit leerem Körper als do {} while (condition) scheint jedoch eine hauptsächlich stilistische Wahl zu sein. Das ist vielleicht klarer in HashMapdas zufällig in Java 8 aktualisiert wurde.

Im Java7 HashMap das kannst du finden:

while (index < t.length && (next = t[index++]) == null)
    ;

Während sich ein Großteil des Codes um ihn herum ebenfalls geändert hat, ist es klar, dass der Ersatz in Java 8 dieser ist:

do {} while (index < t.length && (next = t[index++]) == null);

Die erste Version hat die Schwäche, dass das zufällige Löschen des einsamen Semikolons die Bedeutung des Programms abhängig von der folgenden Zeile ändern würde.

Wie unten zu sehen ist, wird Bytecode generiert von while (...) {} und do {} while (...); ist etwas anders, aber in keiner Weise, die irgendetwas beeinflussen sollte, wenn es ausgeführt wird.

Java-Code:

class WhileTest {
    boolean condition;

    void waitWhile() {
        while(!condition);
    }

    void waitDoWhile() {
        do {} while(!condition);
    }
}

Generierter Code:

class WhileTest {
  boolean condition;

  WhileTest();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  void waitWhile();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field condition:Z
       4: ifne          10
       7: goto          0
      10: return        

  void waitDoWhile();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field condition:Z
       4: ifeq          0
       7: return        
}

  • Aus einer doppelten Frage kommend: Diese IMHO ist eine weitaus bessere und überzeugendere Antwort als die derzeit akzeptierte.

    – Marco13

    19. Mai 2016 um 13:21 Uhr


Abgesehen von potenziellen Leistungsvorteilen gibt es einen klaren Lesbarkeitsvorteil.

Mit while (X) ; das nachgestellte Semikolon nicht immer auf den ersten Blick ersichtlich ist, könnten Sie irrtümlich glauben, dass sich die folgende(n) Anweisung(en) innerhalb der Schleife befinden. Zum Beispiel:

while (x==process(y));
if (z=x) {
    // do stuff.
}

Es wäre sehr leicht, obiges falsch zu interpretieren, da sich die if-Anweisung in der Schleife befindet, und selbst wenn Sie es richtig gelesen hätten, wäre es leicht zu glauben, dass es sich um einen Programmierfehler handelte und das if innerhalb der Schleife stehen sollte.

Mit do {} while(X); obwohl es sofort auf einen Blick klar ist, dass die Schleife keinen Körper hat.

  • Warum nicht einfach schreiben while (x==process(y)) {}? Ich denke, das wäre fast so klar wie die do ... while() Lösung.

    – Auszug

    11. Juli 2014 um 10:36 Uhr


  • @exilit das würde doch helfen weil die do {} while Die Konstruktion hat Schlüsselwörter zwischen den Klammern, die die Trennung sauberer machen. Auch in diesem einfachen Beispiel haben wir bereits 4 Klammern hintereinander )){} – Sie können leicht sehen, dass diese Zahl zunehmen könnte, wenn das if komplizierter wird … also ist es einfacher, das einfach nicht zu bemerken {} mit allen anderen zusammen.

    – Timo B

    11. Juli 2014 um 10:45 Uhr

Benutzer-Avatar
Kein Fehler

Wenn Sie den Kommentar über dem Code lesen, wird erwähnt, dass …

Wenn der Anrufer nicht a ForkJoinTaskist diese Methode verhaltensmäßig äquivalent zu

while (!blocker.isReleasable())
   if (blocker.block())
      return;
}

Es ist also nur eine andere Form, den obigen Code im Else-Teil zu implementieren … !!

Im Style-Notizen es wird erwähnt,

Es gibt mehrere Vorkommen des ungewöhnlichen “do {} while (!cas…)”, das die einfachste Möglichkeit ist, eine Aktualisierung einer CAS-Variablen zu erzwingen.

Und wenn Sie die Umsetzung sehen werden ManagedLocker#isReleasableEs aktualisiert die Sperre und kehrt zurück true wenn eine Blockierung unnötig ist.

Deutung :

Leere While-Schleifen werden verwendet, um einen Interrupt bereitzustellen, bis eine Bedingung auf wahr/falsch zurückgesetzt wird.

Hier, do { } while(!...) ist ein Blocker/Interrupt bis blocker.block() wird sein true Wenn blocker.isReleasable() ist false. Die Schleife setzt die Ausführung fort, solange blocker ist nicht lösbar (!blocker.isReleasable()) und blocker ist nicht gesperrt !! Die Ausführung wird außerhalb der Schleife sein, sobald blocker.block() wird auf wahr gesetzt.

Beachten Sie, dass, do{ } while(...) aktualisiert die CAS-Variable nicht, aber es garantiert, dass das Programm wartet, bis die Variable aktualisiert wird (erzwingt das Warten, bis die Variable aktualisiert wird).

Du kannst so etwas ganz einfach machen mit:

if(true){
 //Do nothing ...
}

1158170cookie-checkTue “nichts” während “Bedingung”

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy