web-dev-qa-db-fra.com

Classe de fil de sécurité dans Java au moyen de blocs synchronisés

Disons que nous avons très simple Java classe MyClass.

public class MyClass {
   private int number;

    public MyClass(int number) {
        this.number = number;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}

Il existe trois façons de construire Thread-Safe Java classe qui a un État:

  1. Rendre vraiment immuable

    public class MyClass {
       private final int number;
    
       public MyClass(int number) {
        this.number = number;
       }
    
       public int getNumber() {
        return number;
       }
    
    }
    
  2. Faire des champs number _ volatile.

    public class MyClass {
       private volatile int number;
    
       public MyClass(int number) {
        this.number = number;
       }
    
       public int getNumber() {
           return number;
       }
    
       public void setNumber(int number) {
           this.number = number;
       }
    }
    
  3. Utilisez un bloc synchronized. Version classique de cette approche décrite au chapitre 4.3.5 de Java Concurrence dans la pratique. Et la chose amusante à ce sujet, il a une erreur dans l'exemple qui est mentionné dans une errata pour ce livre.

    public class MyClass {
       private int number;
    
       public MyClass(int number) {
           setNumber(number);
       }
    
       public synchronized int getNumber() {
           return number;
       }
    
       public synchronized void setNumber(int number) {
           this.number = number;
       }
    }
    

Il y a un autre fait qui devrait être ajouté au contexte de la discussion. Dans un environnement MulthithheDed, JVM est libre de réorganiser des instructions en dehors de synchronized Block préservant une séquence logique et arrive-avant Relations spécifiées par JVM. Il peut provoquer un objet de publication qui n'est pas encore construit correctement à un autre fil.

J'ai quelques questions concernant le troisième affaire.

  1. Sera-t-il équivalent à un morceau de code suivant:

    public class MyClass {
       private int number;
    
       public MyClass(int number) {
           synchronized (this){
               this.number = number;
           }
       }
    
       public synchronized int getNumber() {
           return number;
       }
    
       public synchronized void setNumber(int number) {
           this.number = number;
       }
    }
    
  2. Une réorganisation sera-t-elle évitée dans le troisième cas ou possible pour JVM de réorganiser des intestructions et de publier donc des objets avec une valeur par défaut dans le champ number?

  3. Si une réponse pour la deuxième question est celle de oui que d'avoir une autre question.

     public class MyClass {
       private int number;
    
       public MyClass(int number) {
           synchronized (new Object()){
               this.number = number;
           }
       }
    
       public synchronized int getNumber() {
           return number;
       }
    
       public synchronized void setNumber(int number) {
           this.number = number;
       }
    }
    

Cette zone étrange synchronized (new Object()) est censée empêcher l'effet de réorganisation. Est-ce que ça marchera?

Juste pour être clair, tous ces exemples n'ont aucune application pratique. Je suis juste curieux de nuances de multithreading.

19
wax

synchronized(new Object()) ne fera rien, puisque la synchronisation n'est que sur l'objet que vous synchronisez. Donc, si le thread a Synchronise sur oneObject et thread b synchronise sur anotherObject, il n'y a pas d'événement - avant d'entre eux. Puisque nous pouvons savoir pour un fait qu'aucun autre thread ne se synchronisera jamais sur la fonction new Object() Vous en créez là-bas, cela n'établira pas de se produire - avant d'autres threads.

En ce qui concerne votre synchronzied dans le constructeur, si votre objet est publié en toute sécurité à un autre fil, vous n'en avez pas besoin. Et si ce n'est pas le cas, vous êtes probablement dans un gâchis de problèmes tel qu'il est. J'ai posé cette question à cette question sur la liste d'intérêts de la concurrence il y a un peu, et n thread intéressant résultait . Voir en particulier cet email , qui souligne que même avec votre constructeur synchronisé, en l'absence de publication sûre, un autre thread pourrait voir les valeurs par défaut dans vos champs et cet email qui (IMHO) lie le tout ensemble.

8
yshavit

En question n ° 3, synchronized(new Object()) est un non-op et n'empêchera rien. Le compilateur peut déterminer qu'aucun autre thread ne pourrait éventuellement se synchroniser sur cet objet (car rien d'autre ne peut accéder à l'objet.) Il s'agit d'un exemple explicite dans le papier de Brian Goetz " théorie de Java et pratique: optimisations de synchronisation dans Mustang ".

Même si vous avez besoin de synchroniser dans un constructeur, et même si votre synchronized(new Object()) block était utile - c'est-à-dire que vous avez été synchronisée sur un objet de longue durée de vie différent, depuis votre D'autres méthodes sont synchronisantes sur this, vous avez des problèmes de visibilité si vous ne synchronisez pas sur la même variable. C'est-à-dire que vous voulez bien vouloir que votre constructeur utilise également synchronized(this).

Un de côté:

La synchronisation sur this est considérée comme une mauvaise forme. Au lieu de cela, se synchronisez sur un champ final privé. Les appelants peuvent se synchroniser sur votre objet, ce qui pourrait conduire à une impasse. Considérer ce qui suit:

public class Foo
{
    private int value;
    public synchronized int getValue() { return value; }
    public synchronized void setValue(int value) { this.value = value; }
}

public class Bar
{
    public static void deadlock()
    {
        final Foo foo = new Foo();
        synchronized(foo)
        {
            Thread t = new Thread() { public void run() { foo.setValue(1); } };
            t.start();
            t.join();
        }
    }
}

Ce n'est pas évident envers les appelants de la classe Foo que cela irait de l'impasse. Mieux vaut garder votre sémantique de verrouillage interne et privée à votre classe.

2
Edward Thomson