web-dev-qa-db-fra.com

Si je synchronise deux méthodes sur la même classe, peuvent-elles être exécutées simultanément?

Si je synchronise deux méthodes sur la même classe, peuvent-elles être exécutées 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 exécuter methodA() deux fois sur le même objet dans deux threads différents. même chose dans methodB().

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

110
Shelef

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 threads différents (l'une des deux méthodes bloquera jusqu'à la fin de l'autre).

105
NPE

Dans l'exemple, methodA et methodB sont des méthodes d'instance (par opposition aux méthodes statiques). Mettre synchronized sur 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 différents threads appellent ces méthodes simultanément sur le même objet, ces threads s'affronteront pour le même verrou. Lorsqu'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 doivent utiliser différents verrous, 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 de bloc synchronisé permet de spécifier un objet spécifique dont le thread d'exécution doit acquérir le verrou intrinsèque pour pouvoir 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 tutoriel Java décrit la relation:

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.

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

Le thread Java acquiert un verrou au niveau de l'objet lorsqu'il entre dans une instance synchronisé Java méthode et acquiert un verrou au niveau classe quand il entre en statique synchronisé Java méthode.

Dans votre cas, les méthodes (instance) appartiennent à la même classe. Ainsi, chaque fois qu'un thread entre dans la méthode ou le bloc synchronisé Java, il acquiert un verrou (l'objet sur lequel la méthode est appelée). Par conséquent, aucune autre méthode ne peut être appelée en même temps sur le même objet tant que la première méthode n'est pas terminée et que le verrou (sur l'objet) n'est pas activé.

14
Srikanth

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 peuvent également 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
        }
    }
}
9
Oleksandr_DJ

De la documentation Oracle link

Rendre les méthodes synchronisées a deux effets:

Tout d'abord, il n'est pas possible que deux appels de méthodes synchronisées sur le même objet soient entrelacés. Lorsqu'un thread exécute une méthode synchronisée pour un objet, tous les autres threads appelant 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 pour tous les threads.

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

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

7
Aditya W

Pensez à votre code comme ci-dessous:

class A {

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

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

Ainsi, synchronisé au niveau de la méthode signifie simplement synchronized (this) . Si un thread exécute une méthode de cette classe, il obtiendrait le verrou avant de lancer l'exécution et le conserverait jusqu'à la fin de l'exécution de la méthode.

Mais puis-je exécuter methodB () sur un thread différent tant que methodA () est toujours fonctionnement? (même objet)

En effet, ce n'est pas possible!

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

4
Khosro Makari

Pour plus de clarté, il est possible que les méthodes synchronisées statiquement et non statiques puissent être exécutées simultanément ou simultanément car l’une d’elles possède un verrou au niveau objet et un autre au niveau classe.

4
pacmanfordinner

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

1
xyz

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

0
coolts

Le clé idée avec synchronisation qui ne s’enfonce pas facilement est qu’elle n’aura d’effet que si des méthodes sont appelées sur le même objet instance - il a déjà été mis en évidence dans les réponses et les commentaires. -

Le programme ci-dessous montre clairement la même chose -

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 manière 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 with *noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects()* commented  

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

 Ouput with *synchronizedEffectiveAsMethodsCalledOnSameObject()* commented  

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

0
Shew

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

0
fastcodejava