web-dev-qa-db-fra.com

Y a-t-il un avantage à utiliser une méthode synchronisée au lieu d'un bloc synchronisé?

Quelqu'un peut-il me dire l'avantage de la méthode synchronisée par rapport au bloc synchronisé avec un exemple?

385
Warrior

Quelqu'un peut-il me dire l'avantage de la méthode synchronisée par rapport au bloc synchronisé avec un exemple? Merci.

L'utilisation de la méthode synchronisée sur le bloc ne présente pas d'avantage évident. 

Peut-être que le seul (mais je n’appellerais pas cela un avantage) est que vous n’avez pas besoin d’inclure la référence à l’objet this.

Méthode:

public synchronized void method() { // blocks "this" from here.... 
    ...
    ...
    ...
} // to here

Bloc:

public void method() { 
    synchronized( this ) { // blocks "this" from here .... 
        ....
        ....
        ....
    }  // to here...
}

Voir? Aucun avantage du tout. 

Les blocs do ont toutefois des avantages par rapport aux méthodes, principalement en termes de flexibilité, car vous pouvez utiliser un autre objet comme verrou alors que la synchronisation de la méthode verrouille tout l’objet.

Comparer: 

// locks the whole object
... 
private synchronized void someInputRelatedWork() {
    ... 
}
private synchronized void someOutputRelatedWork() {
    ... 
}

contre. 

// Using specific locks
Object inputLock = new Object();
Object outputLock = new Object();

private void someInputRelatedWork() {
    synchronized(inputLock) { 
        ... 
    } 
}
private void someOutputRelatedWork() {
    synchronized(outputLock) { 
        ... 
    }
}

De plus, si la méthode augmente, vous pouvez toujours garder la section synchronisée séparée:

 private void method() {
     ... code here
     ... code here
     ... code here
    synchronized( lock ) { 
        ... very few lines of code here
    }
     ... code here
     ... code here
     ... code here
     ... code here
}
413
OscarRyz

La seule différence réelle est qu'un bloc synchronisé peut choisir l'objet sur lequel il se synchronise. Une méthode synchronisée ne peut utiliser que 'this' (ou l'instance de classe correspondante pour une méthode de classe synchronisée). Par exemple, ils sont sémantiquement équivalents:

synchronized void foo() {
  ...
}

void foo() {
    synchronized (this) {
      ...
    }
}

Ce dernier est plus flexible car il peut entrer en concurrence pour le verrou associé de any object, souvent une variable membre. C'est également plus granulaire, car le code simultané peut s'exécuter avant et après le bloc, mais toujours dans la méthode. Bien sûr, vous pourriez tout aussi facilement utiliser une méthode synchronisée en refacturant le code concurrent en méthodes distinctes non synchronisées. Utilisez ce qui rend le code plus compréhensible.

137
jcrossley3

Méthode synchronisée

Avantages:

  • Votre IDE peut indiquer les méthodes synchronisées.
  • La syntaxe est plus compacte.
  • Force le fractionnement des blocs synchronisés en méthodes séparées.

Les inconvénients:

  • Synchronise avec cela et permet ainsi aux étrangers de se synchroniser aussi.
  • Il est plus difficile de déplacer le code en dehors du bloc synchronisé.

Bloc synchronisé

Avantages:

  • Permet d'utiliser une variable privée pour le verrou et de le forcer à rester dans la classe.
  • Les blocs synchronisés peuvent être trouvés en cherchant des références à la variable.

Les inconvénients:

  • La syntaxe est plus compliquée et rend donc le code plus difficile à lire.

Personnellement, je préfère utiliser des méthodes synchronisées avec des classes centrées uniquement sur la chose nécessitant une synchronisation. Cette classe devrait être aussi petite que possible et il devrait donc être facile de vérifier la synchronisation. D'autres ne devraient pas avoir à se soucier de la synchronisation.

75
iny

La principale différence est que si vous utilisez un bloc synchronisé, vous pouvez verrouiller un objet autre que this , ce qui permet d’être beaucoup plus flexible.

Supposons que vous avez une file de messages et plusieurs producteurs et consommateurs de messages. Nous ne voulons pas que les producteurs se mêlent les uns aux autres, mais les consommateurs devraient pouvoir récupérer des messages sans attendre les producteurs . Nous créons simplement un objet

Object writeLock = new Object();

Et à partir de maintenant, chaque fois qu'un producteur veut ajouter un nouveau message, nous nous en tenons à cela:

synchronized(writeLock){
  // do something
}

Les consommateurs peuvent donc toujours lire et les producteurs seront verrouillés.

35
cdecker

Méthode synchronisée

Les méthodes synchronisées ont deux effets.
Tout d'abord, lorsqu'un thread exécute une méthode synchronisée pour un objet, tous les autres threads qui invoquent des méthodes synchronisées pour le même bloc d'objet (suspendre l'exécution) jusqu'à ce que le premier thread soit terminé avec l'objet. 

Deuxièmement, lorsqu'une méthode synchronisée se termine, elle établit automatiquement une relation passe-avant avec tout appel ultérieur d'une méthode synchronisée pour le même objet. Cela garantit que les modifications apportées à l'état de l'objet sont visibles par tous les threads. 

Notez que les constructeurs ne peuvent pas être synchronisés - l'utilisation du mot clé synchronized avec un constructeur est une erreur de syntaxe. Synchroniser les constructeurs n'a pas de sens, car seul le thread qui crée un objet doit y avoir accès pendant sa construction. 

Synchronized Statement

Contrairement aux méthodes synchronisées, les instructions synchronisées doivent spécifier l'objet fournissant le verrou intrinsèque: le plus souvent, je l'utilise pour synchroniser l'accès à une liste ou à une carte, mais je ne souhaite pas bloquer l'accès à toutes les méthodes de l'objet. 

Q: Verrous et synchronisation intrinsèques La synchronisation est construite autour d'une entité interne appelée verrou intrinsèque ou verrou du moniteur. (La spécification de l'API fait souvent référence à cette entité simplement comme un "moniteur".) Les verrous intrinsèques jouent un rôle dans les deux aspects de la synchronisation: appliquer un accès exclusif à l'état d'un objet et établir des relations entre événements qui sont essentielles à la visibilité. 

Chaque objet est associé à un verrou intrinsèque. Par convention, un thread qui nécessite un accès exclusif et cohérent aux champs d'un objet doit acquérir le verrou intrinsèque de l'objet avant d'y accéder, puis libérer le verrou intrinsèque lorsqu'il en a terminé avec lui. Un thread est dit posséder le verrou intrinsèque entre le moment où il a acquis le verrou et libéré le verrou. Tant qu'un thread possède un verrou intrinsèque, aucun autre thread ne peut acquérir le même verrou. L'autre thread se bloquera lorsqu'il tentera d'acquérir le verrou. 

package test;

public class SynchTest implements Runnable {  
    private int c = 0;

    public static void main(String[] args) {
        new SynchTest().test();
    }

    public void test() {
        // Create the object with the run() method
        Runnable runnable = new SynchTest();
        Runnable runnable2 = new SynchTest();
        // Create the thread supplying it with the runnable object
        Thread thread = new Thread(runnable,"thread-1");
        Thread thread2 = new Thread(runnable,"thread-2");
//      Here the key point is passing same object, if you pass runnable2 for thread2,
//      then its not applicable for synchronization test and that wont give expected
//      output Synchronization method means "it is not possible for two invocations
//      of synchronized methods on the same object to interleave"

        // Start the thread
        thread.start();
        thread2.start();
    }

    public synchronized  void increment() {
        System.out.println("Begin thread " + Thread.currentThread().getName());
        System.out.println(this.hashCode() + "Value of C = " + c);
//      If we uncomment this for synchronized block, then the result would be different
//      synchronized(this) {
            for (int i = 0; i < 9999999; i++) {
                c += i;
            }
//      }
        System.out.println("End thread " + Thread.currentThread().getName());
    }

//    public synchronized void decrement() {
//        System.out.println("Decrement " + Thread.currentThread().getName());
//    }

    public int value() {
        return c;
    }

    @Override
    public void run() {
        this.increment();
    }
}

Vérifiez les différentes sorties avec la méthode synchronisée, bloc et sans synchronisation.

29
sudheer

Remarque: Les méthodes et les blocs static synchronized fonctionnent sur l'objet Class.

public class MyClass {
   // locks MyClass.class
   public static synchronized void foo() {
// do something
   }

   // similar
   public static void foo() {
      synchronized(MyClass.class) {
// do something
      }
   }
}
27
Peter Lawrey

Lorsque le compilateur Java convertit votre code source en code octet, il gère les méthodes synchronisées et les blocs synchronisés de manière très différente.

Lorsque la machine virtuelle Java exécute une méthode synchronisée, le processus d'exécution identifie le drapeau ACC_SYNCHRONIZED dans la structure method_info de la méthode, puis acquiert automatiquement le verrou de l'objet, appelle la méthode et libère le verrou. Si une exception se produit, le thread libère automatiquement le verrou.

La synchronisation d'un bloc de méthode, en revanche, contourne la prise en charge intégrée de la JVM pour l'acquisition du verrouillage d'un objet et la gestion des exceptions, et nécessite que la fonctionnalité soit explicitement écrite en code octet. Si vous lisez le code d'octet d'une méthode avec un bloc synchronisé, vous verrez plus d'une douzaine d'opérations supplémentaires pour gérer cette fonctionnalité. 

Ceci montre les appels à générer une méthode synchronisée et un bloc synchronisé:

public class SynchronizationExample {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

    public int synchronizedBlockGet() {
        synchronized( this ) {
            return i;
        }
    }
}

La méthode synchronizedMethodGet() génère le code octet suivant:

0:  aload_0
1:  getfield
2:  nop
3:  iconst_m1
4:  ireturn

Et voici le code d'octet de la méthode synchronizedBlockGet():

0:  aload_0
1:  dup
2:  astore_1
3:  monitorenter
4:  aload_0
5:  getfield
6:  nop
7:  iconst_m1
8:  aload_1
9:  monitorexit
10: ireturn
11: astore_2
12: aload_1
13: monitorexit
14: aload_2
15: athrow

Une différence significative entre la méthode synchronisée et le bloc réside dans le fait que le bloc synchronisé réduit généralement la portée du verrou. La portée du verrou étant inversement proportionnelle aux performances, il est toujours préférable de verrouiller uniquement les sections critiques du code. L'un des meilleurs exemples d'utilisation de bloc synchronisé est double vérification du verrouillage dans Singleton pattern où, au lieu de verrouiller la méthode entière getInstance(), nous ne verrons que la partie critique du code utilisée pour créer l'instance Singleton. Cela améliore considérablement les performances car le verrouillage n’est requis qu’une ou deux fois.

Lorsque vous utilisez des méthodes synchronisées, vous devrez redoubler de prudence si vous combinez des méthodes synchronisées statiques et synchronisées non statiques.

17
Mohammad Adil

Le plus souvent, je l'utilise pour synchroniser l'accès à une liste ou à une carte, mais je ne souhaite pas bloquer l'accès à toutes les méthodes de l'objet.

Dans le code suivant, un thread qui modifie la liste ne bloquera pas l'attente d'un thread qui modifie la carte. Si les méthodes étaient synchronisées sur l'objet, chaque méthode devrait attendre même si les modifications apportées ne sont pas en conflit.

private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();

public void put( String s, Bar b ) {
  synchronized( myMap ) {
    myMap.put( s,b );
    // then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public void hasKey( String s, ) {
  synchronized( myMap ) {
    myMap.hasKey( s );
  }
}

public void add( Foo f ) {
  synchronized( myList ) {
    myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public Thing getMedianFoo() {
  Foo med = null;
  synchronized( myList ) {
    Collections.sort(myList);
    med = myList.get(myList.size()/2); 
  }
  return med;
}
12
Clint

Avec les blocs synchronisés, vous pouvez avoir plusieurs synchroniseurs, de sorte que plusieurs événements simultanés mais non conflictuels puissent se dérouler simultanément.

6
Paul Tomblin

Les méthodes synchronisées peuvent être vérifiées à l'aide de l'API de réflexion. Cela peut être utile pour tester certains contrats, tels que toutes les méthodes du modèle sont synchronisées .

L'extrait suivant imprime toutes les méthodes synchronisées de Hashtable:

for (Method m : Hashtable.class.getMethods()) {
        if (Modifier.isSynchronized(m.getModifiers())) {
            System.out.println(m);
        }
}
6
Kojotak

Souvent, utiliser un verrou au niveau de la méthode est trop grossier. Pourquoi verrouiller un morceau de code qui n’accède à aucune ressource partagée en verrouillant une méthode complète. Étant donné que chaque objet est verrouillé, vous pouvez créer des objets factices pour implémenter la synchronisation au niveau du bloc . Le niveau du bloc est plus efficace car il ne verrouille pas la méthode dans son ensemble.

Voici quelques exemples

Niveau de la méthode

class MethodLevel {

  //shared among threads
SharedResource x, y ;

public void synchronized method1() {
   //multiple threads can't access
}
public void synchronized method2() {
  //multiple threads can't access
}

 public void method3() {
  //not synchronized
  //multiple threads can access
 }
}

Niveau du bloc

class BlockLevel {
  //shared among threads
  SharedResource x, y ;

  //dummy objects for locking
  Object xLock = new Object();
  Object yLock = new Object();

    public void method1() {
     synchronized(xLock){
    //access x here. thread safe
    }

    //do something here but don't use SharedResource x, y
    // because will not be thread-safe
     synchronized(xLock) {
       synchronized(yLock) {
      //access x,y here. thread safe
      }
     }

     //do something here but don't use SharedResource x, y
     //because will not be thread-safe
    }//end of method1
 }

[Modifier]

Pour Collection comme Vector et Hashtable, ils sont synchronisés alors que ArrayList ou HashMap ne le sont pas et que vous avez besoin de définir un mot clé synchronisé ou d'appeler des méthodes synchronisées de collections:

Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
List myList = Collections.synchronizedList (myList); // single lock for the entire list
5
Maxim Shoustin

Remarque importante sur l'utilisation du bloc synchronisé: faites attention à ce que vous utilisez comme objet de verrouillage!

L'extrait de code de l'utilisateur 2277816 ci-dessus illustre ce point en ce sens qu'une référence à un littéral de chaîne est utilisée comme objet de verrouillage . Réalisez que les littéraux de chaîne sont internés automatiquement en Java et vous devriez commencer à voir le problème: chaque morceau de code qui se synchronise sur le "verrou" littéral, partage le même verrou! Cela peut facilement conduire à des blocages avec des morceaux de code totalement indépendants.

Il ne faut pas être prudent avec les objets String. Les primitives en boîte constituent également un danger, car les méthodes de sélection automatique et valueOf peuvent réutiliser les mêmes objets, en fonction de la valeur.

Pour plus d'informations, voir: https://www.securecoding.cert.org/confluence/display/Java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused

5
Søren Boisen

La seule différence: les blocs synchronisés permettent un verrouillage granulaire contrairement à la méthode synchronisée

En règle générale, synchronized block ou méthodes ont été utilisés pour écrire du code sécurisé pour les threads en évitant les erreurs d’incohérence dans la mémoire. 

Cette question est très ancienne et beaucoup de choses ont changé au cours des 7 dernières années. De nouvelles structures de programmation ont été introduites pour la sécurité des threads.

Vous pouvez assurer la sécurité des threads en utilisant une API de simultanéité avancée au lieu de blocs synchronied. Cette documentation page fournit de bonnes constructions de programmation pour assurer la sécurité des threads.

Verrouiller les objets prend en charge les idiomes de verrouillage qui simplifient de nombreuses applications simultanées.

Les exécuteurs définissent une API de haut niveau pour le lancement et la gestion des threads. Les implémentations d'exécuteur fournies par Java.util.concurrent fournissent une gestion de pool de threads adaptée aux applications à grande échelle.

Les collections simultanées facilitent la gestion de grandes collections de données et peuvent réduire considérablement le besoin de synchronisation.

Les variables atomiques ont des fonctionnalités qui minimisent la synchronisation et permettent d'éviter les erreurs de cohérence de la mémoire.

ThreadLocalRandom (dans JDK 7) permet de générer efficacement des nombres pseudo-aléatoires à partir de plusieurs threads.

Le meilleur remplacement pour synchronisé est ReentrantLock , qui utilise Lock API

Une exclusion mutuelle réentrante verrouille avec le même comportement de base et la même sémantique que le verrou de surveillance implicite auquel on a accès à l'aide de méthodes et d'instructions synchronisées, mais avec des capacités étendues.

Exemple avec des serrures:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

Reportez-vous à Java.util.concurrent et Java.util.concurrent.atomic packages également pour d'autres constructions de programmation. 

Reportez-vous également à cette question connexe:

Synchronisation vs Verrouillage

5
Ravindra babu

La méthode synchronisée est utilisée pour verrouiller tous les objets Le bloc synchronisé est utilisé pour verrouiller un objet spécifique

4
kishore

En général, ils sont généralement les mêmes, à l'exception du fait d'être explicite à propos du moniteur de l'objet utilisé par rapport à l'implicite de cet objet. Un inconvénient des méthodes synchronisées que je pense parfois être négligé est qu'en utilisant la référence "this" pour se synchroniser, vous laissez la possibilité à des objets externes de se verrouiller sur le même objet. Cela peut être un bug très subtil si vous le rencontrez. La synchronisation sur un objet explicite interne ou un autre champ existant peut éviter ce problème en encapsulant complètement la synchronisation.

3
Alex Miller

En cas de méthodes synchronisées, le verrouillage sera acquis sur un objet. Mais si vous utilisez un bloc synchronisé, vous avez la possibilité de spécifier un objet sur lequel le verrou sera acquis.

Exemple :

    Class Example {
    String test = "abc";
    // lock will be acquired on String  test object.
    synchronized (test) {
        // do something
    }

   lock will be acquired on Example Object
   public synchronized void testMethod() {
     // do some thing
   } 

   }
2
Srinu Yarru

Je sais que c’est une vieille question, mais avec ma lecture rapide des réponses ici, je n’ai vraiment vu personne mentionner qu’une méthode synchronized peut être le verrou faux.
De la concurrence de Java en pratique (p. 72):

public class ListHelper<E> {
  public List<E> list = Collections.syncrhonizedList(new ArrayList<>());
...

public syncrhonized boolean putIfAbsent(E x) {
 boolean absent = !list.contains(x);
if(absent) {
 list.add(x);
}
return absent;
}

Le code ci-dessus a l'apparence d'être thread-safe. Cependant, en réalité, ce n'est pas le cas. Dans ce cas, le verrou est obtenu sur l'instance de la classe. Cependant, il est possible que le list soit modifié par un autre thread n'utilisant pas cette méthode. La bonne approche serait d'utiliser 

public boolean putIfAbsent(E x) {
 synchronized(list) {
  boolean absent = !list.contains(x);
  if(absent) {
    list.add(x);
  }
  return absent;
}
}

Le code ci-dessus empêcherait tous les threads d'essayer de modifier list de modifier la liste jusqu'à la fin du bloc synchronisé.

2
aarbor

Comme déjà dit ici, le bloc synchronisé peut utiliser une variable définie par l'utilisateur comme objet verrou, lorsque la fonction synchronisée utilise uniquement "this". Et bien sûr, vous pouvez manipuler des zones de votre fonction qui devraient être synchronisées . Mais tout le monde dit qu'il n'y a pas de différence entre une fonction synchronisée et un bloc qui couvre toute la fonction en utilisant "ceci" comme objet verrou Ce n'est pas vrai, la différence réside dans le code d'octet qui sera généré dans les deux situations. En cas d'utilisation synchronisée des blocs, il convient d'attribuer une variable locale contenant la référence "this". Et comme résultat, nous aurons une taille un peu plus grande pour la fonction (non pertinent si vous n’avez que peu de fonctions).

Une explication plus détaillée de la différence que vous pouvez trouver ici: http://www.artima.com/insidejvm/ed2/threadsynchP.html

2
Roman

Sur le plan pratique, l’avantage des méthodes synchronisées sur les blocs synchronisés est qu’elles sont plus résistantes aux idiots; Parce que vous ne pouvez pas choisir un objet arbitraire sur lequel verrouiller, vous ne pouvez pas utiliser la syntaxe de méthode synchronisée pour faire des choses stupides, comme verrouiller un littéral de chaîne ou verrouiller le contenu d'un champ mutable qui est modifié sous les threads.

D'un autre côté, avec les méthodes synchronisées, vous ne pouvez pas empêcher le verrou d'être acquis par un thread pouvant obtenir une référence à l'objet.

Donc, utiliser synchronisé en tant que modificateur sur les méthodes protège mieux votre vache-meneur, alors que l’utilisation de blocs synchronisés en conjonction avec des objets de verrouillage final privés est mieux protéger votre propre code des vacanciers.

D'après un résumé de la spécification Java: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

L'instruction synchronisée (§14.17) calcule une référence à un objet; il tente ensuite d'exécuter une action de verrouillage sur cet objet et ne le fait pas continuez jusqu'à ce que l'action de verrouillage se soit terminée avec succès. ...

Une méthode synchronisée (§8.4.3.5) effectue automatiquement une action de verrouillage quand il est invoqué; son corps n'est pas exécuté tant que l'action de verrouillage n'a pas complété avec succès. Si la méthode est une méthode d'instance, elle verrouille le verrou associé à l'instance pour laquelle il a été appelé (c’est-à-dire l’objet qui sera appelé cela lors de l’exécution de le corps de la méthode). Si la méthode est statique, elle verrouille le verrou associé à l'objet Class qui représente la classe dans dont la méthode est définie. ...

Sur la base de ces descriptions, je dirais que la plupart des réponses précédentes sont correctes et qu'une méthode synchronisée peut être particulièrement utile pour les méthodes statiques, où vous devez sinon trouver comment obtenir l'objet "Class qui représente la classe dans laquelle la méthode était défini. "

Edit: Je pensais à l’origine que c’étaient des citations de la spécification Java actuelle. Précisé que cette page est juste un résumé/explication de la spécification

1
Josiah Yoder

Je suppose que cette question concerne la différence entre Thread Safe Singleton et Initialisation différée avec verrouillage double contrôle. Je me réfère toujours à cet article lorsque je dois implémenter un singleton spécifique.

Eh bien, ceci est un Thread Safe Singleton:

// Java program to create Thread Safe 
// Singleton class 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

 //synchronized method to control simultaneous access 
  synchronized public static GFG getInstance()  
  { 
    if (instance == null)  
    { 
      // if instance is null, initialize 
      instance = new GFG(); 
    } 
    return instance; 
  } 
} 

Avantages:

  1. L'initialisation paresseuse est possible.

  2. C'est thread-safe.

Les inconvénients:

  1. la méthode getInstance () est synchronisée, ce qui ralentit les performances car plusieurs threads ne peuvent pas y accéder simultanément.

Ceci est une initialisation lente avec verrouillage double contrôle:

// Java code to explain double check locking 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

  public static GFG getInstance() 
  { 
    if (instance == null)  
    { 
      //synchronized block to remove overhead 
      synchronized (GFG.class) 
      { 
        if(instance==null) 
        { 
          // if instance is null, initialize 
          instance = new GFG(); 
        } 

      } 
    } 
    return instance; 
  } 
} 

Avantages:

  1. L'initialisation paresseuse est possible.

  2. Il est également thread-safe.

  3. Les performances réduites à cause du mot clé synchronisé sont dépassées.

Les inconvénients:

  1. Pour la première fois, cela peut affecter les performances.

  2. Par contre. de la méthode de double contrôle de verrouillage est supportable, donc il peut être utilisé pour les applications multithreads hautes performances.

Veuillez vous référer à cet article pour plus de détails:

https://www.geeksforgeeks.org/Java-singleton-design-pattern-practices-examples/

0
Alexandre Lage