web-dev-qa-db-fra.com

Ne "rien" en "condition"

En parcourant le code de la version Java 8 de ForkJoinPool (qui présente quelques modifications intéressantes par rapport à Java 7), j'ai rencontré cette construction ( ici ):

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

J'ai du mal à comprendre pourquoi vous l'écrivez comme ceci au lieu de simplement

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

Est-ce juste un choix sémantique/lisibilité, puisque vous pourriez lire la première construction sous la forme do "nothing" while "conditions"? Ou y a-t-il un avantage supplémentaire qui me manque?

69
Erik Vesteraas

Si vous lisez les commentaires en haut du fichier, juste en dessous de la déclaration de classe, il y a une section qui explique l'utilisation de cette construction:

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).
54
MicSim

ForkJoinPool utilise intensivement compareAndSwap... à partir de Sun.misc.Unsafe et la plupart des occurrences de do {} while (...) dans ForkJoinPool peuvent - comme indiqué dans d'autres réponses - être expliquées par ce commentaire sous l'en-tête Notes de style:

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

Le choix d'utiliser l'écriture d'une boucle while- avec un corps vide en tant que do {} while (condition) semble toutefois être un choix essentiellement stylistique. Ceci est peut-être plus clair dans HashMap, qui a été mis à jour dans Java 8.

Dans la version HashMap de Java 7, vous pouvez trouver ceci:

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

Bien qu'une grande partie du code qui l'entoure ait également changé, il est clair que le remplacement dans Java 8 est le suivant:

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

La première version a la faiblesse que si le seul point-virgule était supprimé, le sens du programme en serait modifié en fonction de la ligne suivante.

Comme on le voit ci-dessous, le bytecode généré par while (...) {} et do {} while (...); est légèrement différent, mais ne devrait en aucune manière affecter les résultats de son exécution.

Code Java:

class WhileTest {
    boolean condition;

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

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

Code généré:

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        
}
45
Erik Vesteraas

Outre les avantages potentiels en termes de performances, il existe un avantage évident en termes de lisibilité.

Avec while (X) ;, le point-virgule final n’est pas toujours évident au premier abord, vous pouvez être dérouté en pensant que la ou les déclarations suivantes sont dans la boucle. Par exemple:

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

Il serait très facile de mal interpréter ce qui précède comme ayant l'instruction if dans la boucle, et même si vous l'aviez lue correctement, il serait facile de penser que c'était une erreur de programmation et que le if devrait être dans la boucle.

Avec do {} while(X);, il est tout de suite clair qu’il n’ya pas de corps à la boucle.

8
Tim B

Si vous lisez le commentaire ci-dessus le code, il est mentionné que ...

Si l'appelant n'est pas une ForkJoinTask, cette méthode équivaut comportementalement à

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

Donc c'est juste une autre forme pour implémenter le code ci-dessus dans une autre partie ... !!

Dans Notes de style il est mentionné que, 

Il existe plusieurs occurrences de l'inhabituel "do {} while (! Cas ...)", qui constitue le moyen le plus simple de forcer la mise à jour d'un Variable CAS'ed.

Et si vous voyez l'implémentation de ManagedLocker # isReleasable , il met à jour le verrou et renvoie true si le blocage est inutile.

Interprétation :

Les boucles while tout sont utilisées pour fournir une interruption jusqu'à ce que certaines conditions soient réinitialisées sur true/false.

Ici, do { } while(!...) est un bloqueur/interruption jusqu'à ce que blocker.block() soit true lorsque blocker.isReleasable() est false. La boucle continuera l'exécution tant que blocker ne sera pas libérable (!blocker.isReleasable()) et que blocker ne sera pas bloqué !! L'exécution sera interrompue dès que blocker.block() sera défini sur true.

Notez que, do{ } while(...) ne met pas à jour la variable CAS, mais garantit que le programme attendra que la variable soit mise à jour (forcer à attendre que la variable soit mise à jour). 

4
Not a bug

Vous pouvez facilement faire quelque chose comme ça avec:

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