web-dev-qa-db-fra.com

Java: Wait () libère le verrou du bloc synchronisé

J'avais l'impression que wait () libère tous les verrous mais j'ai trouvé ce post qui dit

"L'invocation de l'attente à l'intérieur d'une méthode synchronisée est un moyen simple d'acquérir le verrou intrinsèque"

Veuillez préciser que je suis un peu confus.

http://docs.Oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

55
Abhijit

"L'appel à l'attente dans une méthode synchronisée est un moyen simple d'acquérir le verrou intrinsèque"

Cette phrase est fausse, c'est une erreur dans la documentation.

Thread acquiert le verrou intrinsèque quand il entre une méthode synchronisée. Le thread à l'intérieur de la méthode synchronisée est défini comme le propriétaire du verrou et est dans l'état EXÉCUTABLE . Tout thread qui tente d'entrer dans la méthode verrouillée devient BLOQUÉ .

Lorsque les appels de thread attendent, il libère le verrou d'objet actuel (il conserve tous les verrous des autres objets) et passe à l'état EN ATTENTE .

Lorsque certains autres appels de thread notifient ou notifient All sur ce même objet, le premier thread change d'état de WAITING en BLOCKED, le thread notifié ne ré-acquiert pas automatiquement le verrou ou ne devient RUNNABLE, en fait, il doit se battre pour le verrouillage avec tous les autres threads bloqués.

Les états WAITING et BLOCKED empêchent tous deux l'exécution du thread, mais ils sont très différents.

Les threads en attente doivent être explicitement transformés en threads BLOQUÉS par une notification d'un autre thread.

L'ATTENTE ne va jamais directement à RUNNABLE.

Lorsque le thread RUNNABLE libère le verrou (en quittant le moniteur ou en attendant), l'un des threads BLOQUÉS prend automatiquement sa place.

Donc, pour résumer, le thread acquiert le verrou lorsqu'il entre dans la méthode synchronisée ou lorsqu'il rentre dans la méthode synchronisée après l'attente.

public synchronized guardedJoy() {
    // must get lock before entering here
    while(!joy) {
        try {
            wait(); // releases lock here
            // must regain the lock to reentering here
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}
144
cohadar

J'ai préparé une petite classe de test (du code très sale, désolé) pour démontrer que l'attente libère réellement le verrou.

public class Test {
    public static void main(String[] args) throws Exception {
        testCuncurrency();
    }

    private static void testCuncurrency() throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new Thread(new WaitTester(lock));
        Thread t2 = new Thread(new WaitTester(lock));
        t1.start();
        t2.start();
        Thread.sleep(15 * 1000);
        synchronized (lock) {
            System.out.println("Time: " + new Date().toString()+ ";" + "Notifying all");
            lock.notifyAll();
        }
    }

    private static class WaitTester implements Runnable {
        private Object lock;
        public WaitTester(Object lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            try {
                synchronized (lock) {
                    System.out.println(getTimeAndThreadName() + ":only one thread can be in synchronized block");
                    Thread.sleep(5 * 1000);

                    System.out.println(getTimeAndThreadName() + ":thread goes into waiting state and releases the lock");
                    lock.wait();

                    System.out.println(getTimeAndThreadName() + ":thread is awake and have reacquired the lock");

                    System.out.println(getTimeAndThreadName() + ":syncronized block have finished");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private static String getTimeAndThreadName() {
        return "Time: " + new Date().toString() + ";" + Thread.currentThread().getName();
    }
}

L'exécution de cette classe sur ma machine renvoie le résultat suivant:

Time: Tue Mar 29 09:16:37 EEST 2016;Thread-0:only one thread can be in synchronized block
Time: Tue Mar 29 09:16:42 EEST 2016;Thread-0:thread goes into waiting state and releases the lock
Time: Tue Mar 29 09:16:42 EEST 2016;Thread-1:only one thread can be in synchronized block
Time: Tue Mar 29 09:16:47 EEST 2016;Thread-1:thread goes into waiting state and releases the lock
Time: Tue Mar 29 09:16:52 EEST 2016;Notifying all
Time: Tue Mar 29 09:16:52 EEST 2016;Thread-1:thread is awake and have reacquired the lock
Time: Tue Mar 29 09:16:57 EEST 2016;Thread-1:syncronized block have finished
Time: Tue Mar 29 09:16:57 EEST 2016;Thread-0:thread is awake and have reacquired the lock
Time: Tue Mar 29 09:17:02 EEST 2016;Thread-0:syncronized block have finished
18

wait :: fait partie de la classe Java.lang.Object, nous pouvons donc appeler cette méthode uniquement sur l'objet. appeler cela nécessite un moniteur (verrouillage) sur cet objet, sinon IllegalMonitorStateException sera levé, par exemple) Thread.currentThread (). wait () lèvera cette exception au code ci-dessous.

   Example1
   public void doSomething() {                                          Line 1
        synchronized(lockObject) { //lock acquired                      Line 2
            lockObject.wait();     // NOT Thread.currentThread().wait() Line 3
        }
    }

Maintenant, appeler wait à la ligne 3 libérera le verrou acquis à la ligne 2. Ainsi, tout autre thread entré dans la ligne 1 et en attente d'acquérir le verrou sur lockObject acquerra ce verrou et continuera.

Considérons maintenant ceci Example2; ici, seul le verrou lockObject2 est libéré, et le thread actuel détient toujours le verrou lockObject1. Cela conduira à une impasse; L'utilisateur doit donc être plus prudent dans ce cas.

   Example2 
        public void doSomething() {                                     Line 1
             synchronized(lockObject1) { //lock1 acquired               Line 2
                 synchronized(lockObject2) { //lock2 acquired           Line 3
                     lockObject2.wait();                                Line 4
                 }
             }
        }

Si cette attente est remplacée par sleep, yield, or join, Ils n'ont pas la possibilité de libérer le verrou. Seule l'attente peut libérer le verrou qu'il détient.

Juste prudent sur t1.sleep()/t1.yield() où sont les api statiques et toujours l'action sera effectuée sur le currentThread pas sur le thread t1.

Comprenons alors quelle est la différence entre suspend et ces api sleep, yield, join; parce que suspend est déconseillé pour éviter que la situation du thread ne tienne le verrou, ce qui entraînera un blocage lorsqu'il sera suspendu (pas en cours d'exécution) pendant une durée indéfinie. Il en va de même pour les autres API.

La réponse est suspendre/reprendre sera effectuée sur d'autres threads, comme t1.suspend() où ces api suspendent la Thread.currentThread(). Par conséquent, l'utilisateur a une mise en garde sur le fait de ne pas tenir de verrou avant d'appeler ces API pour éviter un blocage. Ce n'est pas le cas lors de l'appel de suspend. Le thread appelé ne connaît pas l'état du thread appelant (verrouillage) sur lequel il va effectuer la suspension, donc obsolète.

6
Kanagavelu Sugumar

Je pense que cette déclaration doit être considérée dans son contexte complet.

Lorsqu'un thread appelle d.wait, il doit posséder le verrou intrinsèque pour d - sinon une erreur est levée. L'appel d'attente dans une méthode synchronisée est un moyen simple d'acquérir le verrou intrinsèque.

Je comprends qu'ils devraient simplifier cela pour être comme:

L'invocation des méthodes synchronized acquiert un verrou sur l'objet, nous pouvons simplement mettre une invocation wait() à l'intérieur d'une méthode synchronized.

2
Yogi