recherche

Maison  >  Questions et réponses  >  le corps du texte

并发 - Java的AQS.Node源码疑惑

AbstractQueuedSynchronizerNode内部类中,对volatile Node prev成员变量获取方法predecessor()如下

   
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

在源码中,这里对volatile类型的成员变量prev的返回,是先把他赋值给一个中间变量p,然后拿p返回。
这种设计在AQS的源码中很多地方都有涉及到,包括在其它源码中也经常看到对volatile类型的变量先赋值给另外一个变量,然后把这个变量返回.
这样设计的目的是什么?

大家讲道理大家讲道理2814 Il y a quelques jours882

répondre à tous(2)je répondrai

  • 迷茫

    迷茫2017-04-17 18:02:30

    // Works with acquire/release semantics for volatile in Java 1.5 and later 
    // Broken under Java 1.4 and earlier semantics for volatile 
    class Foo {
        private volatile Helper helper;
        public Helper getHelper() {
            Helper result = helper;
            if (result == null) {
                synchronized(this) {
                    result = helper;
                    if (result == null) {
                        helper = result = new Helper();
                    }
                }
            }
            return result;
        }
    // other functions and members... }
    

    Notez le résultat de la variable locale, qui semble inutile. L'effet de ceci est que dans les cas où l'assistant est déjà initialisé (c'est-à-dire la plupart du temps), le champ volatile n'est accédé qu'une seule fois (en raison du « résultat de retour ; » au lieu de « assistant de retour ; »), ce qui peut améliorer le la performance globale de la méthode jusqu'à 25 pour cent.[6]

    Si l'objet d'assistance est statique (un par chargeur de classe), une alternative est l'idiome du titulaire d'initialisation à la demande[7] (voir le listing 16.6[8] du texte cité précédemment.)

    -------Wikipédia

    répondre
    0
  • 伊谢尔伦

    伊谢尔伦2017-04-17 18:02:30

    Dans cette méthode predecessor(), l'effet de Node p n'est pas si évident. S'il vous plaît, permettez-moi de rendre l'exemple un peu plus extrême :

    final Node extremePredecessor() throws NullPointerException {
        // #L0: Node p = prev;
        // #L1
        if (crazyConditional_1(prev))  {
          ...
        }
        // #L2
        else if (crazyConditional_2(prev))  {
          ...
        }        
        // #L3
        else if (crazyConditional_3(prev)) {
          ...
        }
        // #L4
        else {
          return prev;
        }
    }

    En supposant que 100 appels de threads changeront la valeur de prev, alors entre #L1 et #L4, toute modification apportée à la variable partagée -- prev sera visible par extremePredecessor().
    Cela posera les problèmes suivants :

    • est très similaire à un verrou de synchronisation. Les mises à jour synchrones de prev entraîneront des pertes de performances. Prev deviendra un goulot d'étranglement pour l'ensemble de la file d'attente.

    • La valeur de prev entre #L1 et #L4 peut être incohérente car d'autres threads l'ont modifiée. Cela rend beaucoup plus difficile la compréhension du code.

    Si vous utilisez Node p = prev;, alors après #L0, il n'est pas nécessaire de synchroniser la valeur de p. p de #L1 à #L4 sont également cohérents.

    Pour volatile, voir :
    Mot clé volatile Java Language Spec
    https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls- 8.3 .1.4

    répondre
    0
  • Annulerrépondre