Si j'ai synchronisé deux méthodes sur la même classe, peuvent-elles s'exécuter simultanément?

164

Si j'ai synchronisé deux méthodes sur la même classe, peuvent-elles s'exécuter simultanément sur le même objet ? par exemple:

class A {
    public synchronized void methodA() {
        //method A
    }

    public synchronized void methodB() {
        // method B
    }
}

Je sais que je ne peux pas courir methodA()deux fois sur le même objet dans deux threads différents. même chose en methodB().

Mais puis-je exécuter methodB()sur un thread différent pendant qu'il methodA()est toujours en cours d'exécution? (même objet)

Shelef
la source

Réponses:

148

Les deux méthodes verrouillent le même moniteur. Par conséquent, vous ne pouvez pas les exécuter simultanément sur le même objet à partir de différents threads (l'une des deux méthodes se bloquera jusqu'à ce que l'autre soit terminée).

NPE
la source
1
J'avais un ajout à cette question. Supposons que les deux méthodes soient statiques maintenant que methodA est appelée en utilisant Class tandis que methodB est appelée en utilisant un objet comme A.methodA () dans t1 et obj.methodB () dans t2. Que va-t-il se passer maintenant, vont-ils bloquer ????
amod
2
@ amod0017: obj.methodB()est synonyme de A.methodB()quand methodB()est static. Donc oui, ils bloqueront (sur le moniteur de la classe, pas de l'objet).
NPE
essaiera d'y revenir. :)
amod
@NPE Donc, même si les deux méthodes sont statiques et que 2 threads t1 et t2 sur le même objet essaient d'appeler simultanément methodA () et methodB (), alors seulement 1 thread (disons t1) s'exécutera et l'autre thread devra attendre que t1 libère le verrou ?
sreeprasad
8
Gardez à l'esprit que les méthodes statiques utilisent le verrouillage sur l' .classobjet. Donc si vous avez class A {static synchronized void m() {} }. Et puis un thread l'appelle new A().m()acquiert un verrou sur l' new A()objet. Si puis un autre thread appelle A.m()ce CONCLUT LA MÉTHODE PAS DE PROBLÈME parce que ce qu'il cherche est verrou sur l' A.classobjet en NO FILETS possèdent ce type de serrure . Donc, même si vous avez déclaré la méthode, synchronizedelle est en fait accessible par deux threads différents EN MÊME TEMPS . Ainsi: ne jamais utiliser de références d'objet pour appeler des méthodes statiques
Alex Semeniuk
113

Dans l'exemple, methodA et methodB sont des méthodes d'instance (par opposition aux méthodes statiques). Mettre synchronizedsur une méthode d'instance signifie que le thread doit acquérir le verrou (le "verrou intrinsèque") sur l'instance d'objet sur laquelle la méthode est appelée avant que le thread ne puisse commencer à exécuter le code de cette méthode.

Si vous avez deux méthodes d'instance différentes marquées comme synchronisées et que des threads différents appellent ces méthodes simultanément sur le même objet, ces threads seront en compétition pour le même verrou. Une fois qu'un thread obtient le verrou, tous les autres threads sont exclus de toutes les méthodes d'instance synchronisées sur cet objet.

Pour que les deux méthodes s'exécutent simultanément, elles devraient utiliser des verrous différents, comme ceci:

class A {
    private final Object lockA = new Object();
    private final Object lockB = new Object();

    public void methodA() {
        synchronized(lockA) {
            //method A
        }
    }

    public void methodB() {
        synchronized(lockB) {
            //method B
        }
    }
}

où la syntaxe du bloc synchronisé permet de spécifier un objet spécifique dont le thread en cours d'exécution a besoin pour acquérir le verrou intrinsèque afin d'entrer dans le bloc.

La chose importante à comprendre est que même si nous mettons un mot-clé «synchronisé» sur des méthodes individuelles, le concept de base est le verrou intrinsèque dans les coulisses.

Voici comment le didacticiel Java décrit la relation:

La synchronisation est construite autour d'une entité interne connue sous le nom de verrou intrinsèque ou de verrouillage du moniteur. (La spécification de l'API se réfère souvent à cette entité simplement comme un «moniteur».) Les verrous intrinsèques jouent un rôle dans les deux aspects de la synchronisation: imposer un accès exclusif à l'état d'un objet et établir des relations d'avance qui sont essentielles à la visibilité.

Chaque objet est associé à un verrou intrinsèque. Par convention, un thread qui a besoin d'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 eux. On dit qu'un thread possède 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.

Le but du verrouillage est de protéger les données partagées. Vous utiliseriez des verrous séparés comme indiqué dans l'exemple de code ci-dessus uniquement si chaque verrou protégeait des membres de données différents.

Nathan Hughes
la source
donc dans cet exemple, le verrou est sur les objets lockA \ lockB et non sur la classe A? S'agit-il d'un exemple de verrouillage au niveau de la classe ?
Nimrod
2
@Nimrod: il verrouille sur les objets lockA et lockB et non sur l'instance de A. rien ici ne se verrouille sur une classe. le verrouillage au niveau de la classe signifierait obtenir le verrou sur un objet de classe, en utilisant quelque chose comme static synchronizedousynchronized (A.class)
Nathan Hughes
Voici le lien vers le tutoriel java expliquant exactement ce qui est répondu ici.
Alberto de Paola
18

Java Thread acquiert un verrou au niveau de l'objet lorsqu'il entre dans une méthode Java synchronisée par instance et acquiert un verrou au niveau de la classe lorsqu'il entre dans une méthode Java synchronisée statique .

Dans votre cas, les méthodes (instance) sont de même classe. Ainsi, chaque fois qu'un thread entre dans la méthode synchronisée java ou dans un bloc, il acquiert un verrou (l'objet sur lequel la méthode est appelée). Donc, une autre méthode ne peut pas être appelée en même temps sur le même objet jusqu'à ce que la première méthode soit terminée et que le verrouillage (sur l'objet) soit libéré.

Srikanth
la source
si j'ai deux threads sur deux instances différentes de la classe, ils pourront alors exécuter les deux méthodes simultanément de sorte qu'un thread appelle une méthode synchronisée et l'autre appelle la deuxième méthode synchronisée. Si ma compréhension est correcte, puis-je utiliser private final Object lock = new object();avec synchronized pour permettre à un seul thread de pouvoir exécuter l'une des méthodes? Merci
Yug Singh
13

Dans votre cas, vous avez synchronisé deux méthodes sur la même instance de classe. Ainsi, ces deux méthodes ne peuvent pas s'exécuter simultanément sur différents threads de la même instance de classe A. Mais elles le peuvent sur différentes instances de classe A.

class A {
    public synchronized void methodA() {
        //method A
    }
}

est le même que:

class A {
    public void methodA() {
        synchronized(this){
            // code of method A
        }
    }
}
Oleksandr_DJ
la source
Et si je définis un verrou comme private final Object lock = new Object();et que je l' utilise maintenant lockavec un bloc synchronisé dans deux méthodes, votre déclaration sera-t-elle vraie? IMO puisque Object est la classe parente de tous les objets, donc même si les threads sont sur une instance différente de la classe, un seul peut accéder au code à l'intérieur du bloc synchronisé à la fois. Merci.
Yug Singh
Si vous définissez le "verrou d'objet final privé" dans la classe et que vous vous synchronisez avec lui, vous remplissez un verrou par instance de classe, il se comportera donc de la même manière que synchronized (this).
Oleksandr_DJ
Oui, Object est un parent pour toutes les classes, mais l'instance «verrouiller» dans votre cas est «instance par classe propriétaire», donc, elle a le même effet que «ceci» pour la synchronisation.
Oleksandr_DJ
7

À partir du lien de documentation d'Oracle

La synchronisation des méthodes a deux effets:

Premièrement, 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 objet bloquent (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 d'avance 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 par tous les threads

Cela répondra à votre question: sur le même objet, vous ne pouvez pas appeler la deuxième méthode synchronisée lorsque la première exécution de la méthode synchronisée est en cours.

Consultez cette page de documentation pour comprendre les verrous intrinsèques et le comportement des verrous.

Aditya W
la source
6

Pensez à votre code comme ci-dessous:

class A {

public void methodA() {
    synchronized(this){        
      //method A body
    }
}

public void methodB() {
    synchronized(this){
      // method B body
    }
}

Donc, synchronisé au niveau de la méthode signifie simplement synchronisé (ceci). si un thread exécute une méthode de cette classe, il obtiendrait le verrou avant de démarrer l'exécution et le maintiendrait jusqu'à ce que l'exécution de la méthode soit terminée.

Mais puis-je exécuter methodB () sur un thread différent pendant que methodA () est toujours en cours d'exécution? (même objet)

En effet, ce n'est pas possible!

Par conséquent, plusieurs threads ne pourront pas exécuter un nombre quelconque de méthodes synchronisées sur le même objet simultanément.

Khosro Makari
la source
et si je crée des threads sur deux objets différents de la même classe? Dans ce cas, si j'appelle une méthode à partir d'un thread et une autre méthode à partir du deuxième thread, ne s'exécuteront-elles pas simultanément?
Yug Singh
2
Ils le feront car ce sont des objets différents. Autrement dit, si vous souhaitez l'empêcher, vous pouvez utiliser des méthodes statiques et synchroniser la classe ou utiliser un objet de variable de classe comme verrou ou créer la classe Singleton. @Yug Singh
Khosro Makari
4

Pour toute clarté, il est possible que les méthodes synchronisées statiques et non statiques puissent s'exécuter simultanément ou simultanément, car l'une d'entre elles a un verrouillage au niveau de l'objet et un autre verrouillage au niveau de la classe.

pacmanfordinner
la source
3

L' idée clé de la synchronisation qui ne s'enfonce pas facilement est qu'elle n'aura d'effet que si les méthodes sont appelées sur la même instance d' objet - elle a déjà été mise en évidence dans les réponses et les commentaires -

Ci-dessous l'exemple de programme est de clairement identifier le même

public class Test {

public synchronized void methodA(String currentObjectName) throws InterruptedException {
    System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodA in");
    Thread.sleep(1000);
    System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodA out");
}

public synchronized void methodB(String currentObjectName)  throws InterruptedException {
    System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodB in");
    Thread.sleep(1000);
    System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodB out");
}

public static void main(String[] args){
    Test object1 = new Test();
    Test object2 = new Test();
    //passing object instances to the runnable to make calls later
    TestRunner runner = new TestRunner(object1,object2);
    // you need to start atleast two threads to properly see the behaviour
    Thread thread1 = new Thread(runner);
    thread1.start();
    Thread thread2 = new Thread(runner);
    thread2.start();
}
}

class TestRunner implements Runnable {
Test object1;
Test object2;

public TestRunner(Test h1,Test h2) {
    this.object1 = h1;
    this.object2 = h2;
}

@Override
public void run() {
    synchronizedEffectiveAsMethodsCalledOnSameObject(object1);
    //noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects(object1,object2);
}

// this method calls the method A and B with same object instance object1 hence simultaneous NOT possible
private void synchronizedEffectiveAsMethodsCalledOnSameObject(Test object1) {
    try {
        object1.methodA("object1");
        object1.methodB("object1");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

// this method calls the method A and B with different object instances object1 and object2 hence simultaneous IS possible
private void noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects(Test object1,Test object2) {
    try {
        object1.methodA("object1");
        object2.methodB("object2");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
}

Notez la différence dans la sortie de la façon dont l'accès simultané est autorisé comme prévu si les méthodes sont appelées sur différentes instances d'objet.

Ouput avec noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects () commenté -la sortie est dans l'ordre methodA in> methodA Out .. methodB in> methodB Out Ouput avec * noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects () * commenté

et Ouput avec synchronizedEffectiveAsMethodsCalledOnSameObject () commenté - la sortie montre l'accès simultané de methodA par Thread1 et Thread0 dans la section en surbrillance -

Ouput avec * synchronizedEffectiveAsMethodsCalledOnSameObject () * commenté

L'augmentation du nombre de threads le rendra encore plus visible.

somshivam
la source
2

Non, ce n'est pas possible, si cela était possible, les deux méthodes pourraient mettre à jour la même variable simultanément, ce qui pourrait facilement corrompre les données.

fastcodejava
la source
2

Oui, ils peuvent exécuter simultanément les deux threads. Si vous créez 2 objets de la classe car chaque objet contient un seul verrou et chaque méthode synchronisée nécessite un verrou. Donc, si vous souhaitez exécuter simultanément, créez deux objets, puis essayez de l'exécuter en utilisant ces références d'objet.

cool
la source
1

Vous le synchronisez sur un objet et non sur une classe. Donc ils ne peuvent pas courir simultanément sur le même objet

xyz
la source
0

Deux threads différents exécutant une méthode synchronisée commune sur l'objet unique, puisque l'objet est le même, lorsqu'un thread l'utilise avec une méthode synchronisée, il devra varifier le verrou, si le verrou est activé, ce thread passera à l'état d'attente, si le verrou est désactivé, il peut accéder à l'objet, tandis qu'il y accédera, il activera le verrou et ne le libérera que lorsque son exécution sera terminée. lorsque l'autre thread arrive, il variera le verrou, puisqu'il est activé, il attendra que le premier thread termine son exécution et libère le verrou mis sur l'objet, une fois le verrou libéré, le deuxième thread aura accès à l'objet et il activera le verrou jusqu'à son exécution. donc l'exécution ne sera pas simultanée, les deux threads s'exécuteront un par un,

Ankit yadav
la source
1
Veuillez ponctuer et capitaliser correctement ce désordre. Il n'existe pas de mot tel que «varify».
Marquis of Lorne