Java méthode synchronisée verrou sur l'objet, ou de la méthode?


Si j'ai 2 méthodes synchronisées dans la même classe, mais que chacune accède à des variables différentes, 2 threads peuvent-ils accéder à ces 2 méthodes en même temps? Le verrouillage se produit-il sur l'objet ou devient-il aussi spécifique que les variables à l'intérieur de la méthode synchronisée?

Exemple:

class X {

    private int a;
    private int b;

    public synchronized void addA(){
        a++;
    }

    public synchronized void addB(){
        b++;
    }

}

2 threads peuvent-ils accéder à la même instance de classe X exécutant x.addA() et x.addB() en même temps?

Author: informatik01, 2010-06-15

10 answers

Si vous déclarez la méthode comme synchronisée (comme vous le faites en tapant public synchronized void addA()), vous synchronisez sur l'objet entier, donc deux threads accédant à une variable différente de ce même objet se bloqueraient de toute façon.

Si vous souhaitez synchroniser uniquement sur une variable à la fois, afin que deux threads ne se bloquent pas lors de l'accès à différentes variables, vous devez les synchroniser séparément dans des blocs synchronized (). Si a et b étaient des références d'objet, vous feriez utilisation:

public void addA() {
    synchronized( a ) {
        a++;
    }
}
public void addB() {
    synchronized( b ) {
        b++;
    }
}

Mais comme ce sont des primitives, vous ne pouvez pas le faire.

Je vous suggère d'utiliser AtomicInteger à la place:

import java.util.concurrent.atomic.AtomicInteger;
class X {
    AtomicInteger a;
    AtomicInteger b;
    public void addA(){
        a.incrementAndGet();
    }
    public void addB(){ 
        b.incrementAndGet();
    }
}
 152
Author: OscarRyz, 2015-04-17 18:14:31

Synchronisé sur la déclaration de méthode est du sucre syntaxique pour cela:

 public void addA() {
     synchronized (this) {
          a++;
     }
  }

Sur une méthode statique, c'est du sucre syntaxique pour cela:

 ClassA {
     public static void addA() {
          synchronized(ClassA.class) {
              a++;
          }
 }

Je pense que si les concepteurs Java savaient alors ce qui est compris maintenant à propos de la synchronisation, ils n'auraient pas ajouté le sucre syntaxique, car cela conduit le plus souvent à de mauvaises implémentations de concurrence.

 50
Author: Yishai, 2016-09-27 10:18:03

Le verrou accédé est sur l'objet, pas sur la méthode. Les variables accessibles dans la méthode ne sont pas pertinentes.

Ajouter "synchronized" à la méthode signifie que le thread exécutant le code doit acquérir le verrou sur l'objet avant de continuer. L'ajout de "synchronisé statique" signifie que le thread exécutant le code doit acquérir le verrou sur l'objet de classe avant de continuer. Alternativement, vous pouvez envelopper le code dans un bloc comme celui-ci:

public void addA() {
    synchronized(this) {
        a++;
    }
}

Afin que vous puissiez spécifier l'objet dont la serrure doit être acquis.

Si vous voulez éviter de verrouiller l'objet contenant, vous pouvez choisir entre:

 12
Author: Nathan Hughes, 2017-05-23 12:26:36

De Java SE essentials sur les méthodes synchronisées :

Tout d'abord, il n'est pas possible que deux invocations de méthodes synchronisées sur le même objet s'entrelacent. 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 (suspendent l'exécution) jusqu'à ce que le premier thread soit terminé avec l'objet.

Du Java SE essentials sur synchronized les blocs:

Les instructions synchronisées sont également utiles pour améliorer la concurrence avec une synchronisation fine. Supposons, par exemple, que la classe MsLunch ait deux champs d'instance, c1 et c2, qui ne sont jamais utilisés ensemble. Toutes les mises à jour de ces champs doivent être synchronisées, mais il n'y a aucune raison d'empêcher une mise à jour de c1 d'être entrelacée avec une mise à jour de c2 - et cela réduit la concurrence en créant un blocage inutile. au Lieu d'utiliser synchronisé méthodes ou autrement en utilisant le verrou associé à cela, nous créons deux objets uniquement pour fournir des verrous.

(Emphase à moi.)

Vous avez 2 variables sans entrelacement. Si vous souhaitez accéder à chacun de différents threads en même temps. vous devez définir le verrou non pas sur la classe d'objet elle-même mais sur l'objet de classe comme ci-dessous (exemple du deuxième lien Oracle):

public class MsLunch {

    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}
 9
Author: MehdiMAH, 2017-08-12 07:38:31

De la documentation oracle lien

La synchronisation des méthodes a deux effets:

Tout d'abord, il n'est pas possible pour deux invocations de méthodes synchronisées sur le même objet de s'entrelacer. 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 (suspendent 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 happens-before avec toute invocation ultérieure d'une méthode synchronisée pour le même objet. Cela garantit que les modifications de l'état de l'objet sont visibles à tous les threads

Jetez un oeil à cette documentationpage pour comprendre les verrous intrinsèques et le comportement des verrous.

Cela répondra à votre question: Sur le même objet x, vous ne pouvez pas appeler x. addA () et x. addB() en même temps lorsque l'une des méthodes synchronisées l'exécution est en cours.

 5
Author: Aditya W, 2016-02-21 10:12:15

Vous pouvez faire quelque chose comme ce qui suit. Dans ce cas, vous utilisez le verrou sur a et b pour synchroniser au lieu du verrou sur "this". Nous ne pouvons pas utiliser int car les valeurs primitives n'ont pas de verrous, nous utilisons donc Integer.

class x{
   private Integer a;
   private Integer b;
   public void addA(){
      synchronized(a) {
         a++;
      }
   }
   public synchronized void addB(){
      synchronized(b) {
         b++;
      }
   }
}
 2
Author: dsmith, 2010-06-15 18:05:10

Si vous avez des méthodes qui ne sont pas synchronisées et qui accèdent et modifient les variables d'instance. Dans votre exemple:

 private int a;
 private int b;

N'importe quel nombre de threads peut accéder à ces méthodes non synchronisées en même temps quand un autre thread est dans la méthode synchronisée du même objet et peut apporter des modifications aux variables d'instance. Par exemple: -

 public void changeState() {
      a++;
      b++;
    }

Vous devez éviter le scénario selon lequel les méthodes non synchronisées accèdent aux variables d'instance et les modifient autrement il est inutile d'utiliser des méthodes synchronisées.

Dans le scénario ci-dessous: -

class X {

        private int a;
        private int b;

        public synchronized void addA(){
            a++;
        }

        public synchronized void addB(){
            b++;
        }
     public void changeState() {
          a++;
          b++;
        }
    }

Un seul des threads peut être dans la méthode addA ou addB mais en même temps, n'importe quel nombre de threads peut entrer dans la méthode changeState. Aucun thread ne peut entrer addA et addB en même temps (en raison du verrouillage au niveau de l'objet) mais en même temps, n'importe quel nombre de threads peut entrer changeState.

 2
Author: Goyal Vicky, 2015-08-07 11:09:36

Cet exemple (bien que pas joli) peut fournir plus d'informations sur le mécanisme de verrouillage. If incrementA est synchronisé, et incrementB est pas synchronisés, puis incrementB sera exécutée dès que possible, mais si incrementB est aussi synchronisé il faut ensuite attendre incrementA pour finir, avant de incrementB puisse faire son travail.

Les deux méthodes sont appelées sur un seul objet instance, dans cet exemple, il est: travail, et "concurrent" threads sont aThread et main.

Essayer avec 'synchronisé' dans incrementB et sans elle, et vous verrez des résultats différents.Si incrementB est 'synchronized ' aussi bien alors il doit attendre que incrementA () finisse. Exécuter plusieurs fois chaque variante.

class LockTest implements Runnable {
    int a = 0;
    int b = 0;

    public synchronized void incrementA() {
        for (int i = 0; i < 100; i++) {
            this.a++;
            System.out.println("Thread: " + Thread.currentThread().getName() + "; a: " + this.a);
        }
    }

    // Try with 'synchronized' and without it and you will see different results
    // if incrementB is 'synchronized' as well then it has to wait for incrementA() to finish

    // public void incrementB() {
    public synchronized void incrementB() {
        this.b++;
        System.out.println("*************** incrementB ********************");
        System.out.println("Thread: " + Thread.currentThread().getName() + "; b: " + this.b);
        System.out.println("*************** incrementB ********************");
    }

    @Override
    public void run() {
        incrementA();
        System.out.println("************ incrementA completed *************");
    }
}

class LockTestMain {
    public static void main(String[] args) throws InterruptedException {
        LockTest job = new LockTest();
        Thread aThread = new Thread(job);
        aThread.setName("aThread");
        aThread.start();
        Thread.sleep(1);
        System.out.println("*************** 'main' calling metod: incrementB **********************");
        job.incrementB();
    }
}
 1
Author: Nenad Bulatovic, 2016-12-09 16:52:17

Oui, il bloquera l'autre méthode car la méthode synchronisée s'applique à l'objet de classe ENTIER comme indiqué .... mais de toute façon, il bloquera l'autre exécution de thread UNIQUEMENT lors de l'exécution de la somme dans la méthode addA ou addB qu'il entre, car quand il finit ... l'un thread LIBRE - l'objet et l'autre thread d'accéder à l'autre méthode, et ainsi de suite parfaitement de travail.

Je veux dire que le "synchronisé" est fait précisément pour bloquer l'autre thread d'accéder à un autre lors d'une exécution de code spécifique. DONC, FINALEMENT, CE CODE FONCTIONNERA BIEN.

En guise de note finale, s'il y a des variables 'a' et 'b', pas seulement une variable unique 'a' ou tout autre nom, il n'est pas nécessaire de synchroniser ces méthodes car il est parfaitement sûr d'accéder à un autre var (Autre emplacement de mémoire).

class X {

private int a;
private int b;

public void addA(){
    a++;
}

public void addB(){
    b++;
}}

Fonctionnera aussi bien

 1
Author: Jose Velandia, 2017-05-26 22:29:23

Cela pourrait ne pas fonctionner car la boxe et l'autoboxing de Integer à int et viceversa dépendent de la JVM et il est fort possible que deux nombres différents soient hachés à la même adresse s'ils sont entre -128 et 127.

 0
Author: Sriharsha g.r.v, 2015-07-29 21:49:55