7
votes

Pourquoi est-ce que je reçois une boucle infinie alors que je modifie la variable de verrouillage?

public class GuardedBlock {

    private boolean guard = false;

    private static void threadMessage(String message) {
        System.out.println(Thread.currentThread().getName() + ": " + message);
    }

    public static void main(String[] args) {
        GuardedBlock guardedBlock = new GuardedBlock();

        Thread thread1 = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    guardedBlock.guard = true;
                    threadMessage("Set guard=true");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });

        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                threadMessage("Start waiting");
                while (!guardedBlock.guard) {
                    //threadMessage("Still waiting...");
                }
                threadMessage("Finally!");
            }
        });

        thread1.start();
        thread2.start();
    }
}
I was learning concurrency through java essentials tutorial. Got to guarded blocks and tried to test it. There is one thing I cannot understand.While loop is infinite, but if you uncomment threadMessage line everything works fine. Why?

5 commentaires

Qu'est-ce que vous essayez de faire exactement? Les modifications apportées à Guard ne seront pas visibles pour d'autres threads si elle n'est pas volatile.


Lorsque vous commencez cette ligne, il ne reste plus rien à l'intérieur de votre temps, et Java est suffisamment intelligent pour ignorer la boucle complète. Donc, c'est aussi bon qu'il n'y a pas de boucle.


Que voulez-vous dire par tout fonctionne bien? Est-ce qu'il imprime toujours? finalement ?


Est-ce que cela fonctionne si vous modifiez Guard vers Guard volatile ? De plus, une boucle d'attente occupée est mauvaise, je ne vous recommande pas de les utiliser. Pensez à utiliser attendre et notify à la place.


@AgRAWAL: Cela ne peut pas être vrai. Si c'était le cas, OP ne ferait pas l'expérience d'une boucle infinie. Au contraire, comme Akhil_mittal dise, Guard n'est pas volatile . Donc, sa valeur n'est pas garantie d'être synchronisée entre les threads. L'ajout de la ligne threadmessage doit déclencher une sorte de barrière de mémoire qui se produit pour aider à synchroniser la vue du thread de la valeur de la valeur . Mais c'est un comportement très peu fiable.


3 Réponses :


15
votes

réponse courte

Vous avez oublié de déclarer gardien comme booléen volatile.


Si vous envisagez la déclaration de votre champ Comme volatile , vous ne direz pas le JVM que ce champ peut être vu par plusieurs threads, ce qui est le cas dans votre exemple.

Dans de tels cas, la valeur de Garde sera lu une seule fois et provoquera une boucle infinie. Il sera optimisé à quelque chose comme ceci (sans impression): xxx

maintenant pourquoi system.out.println changer ce comportement? Parce que le écrit est synchronisé qui force les filets à non pas en cache se lit.

ici une pâte du code de l'un des printLn méthode de Imprimanteam utilisé par system.out.println : xxx

et le écriture méthode: xxx

remarque la synchronisation.


0 commentaires

1
votes

La raison pour laquelle votre boucle tandis que votre boucle est infinie est que la condition ! gardéblock.guard est toujours vrai. Cela signifie gardéblock.guard = true; défini dans le thread 1 n'est pas défini pour le fil 2, et cela se produit parce que vous n'utilisez pas la protection variable comme volatile

Permettez-moi de copier le besoin d'utiliser le volatil dans Java de Wikipedia lui-même:

Dans toutes les versions de Java, il existe un ordre global sur les lectures et écrit à une variable volatil. Cela implique que chaque fil accédant à un champ volatille lira sa valeur actuelle avant de continuer , au lieu de (potentiellement) à l'aide d'une valeur mise en cache.

espère que cela aide.


0 commentaires

3
votes

La solution de Jean-Francois est la bonne: vous devez absolument avoir une sorte de synchronisation lorsque les threads accèdent à une variable partagée, que ce soit via volatile code>, synchronisé code>, etc.

J'ajouterais également que votre tandis que code> la boucle est à savoir ce qu'on appelle occupé En attente - c'est-à-dire, testez à plusieurs reprises une condition dans un réglage simultané. La boucle serrée de l'attente occupée dans ce code peut porter la CPU. Au moins, ses effets sur les ressources du système seront imprévisibles. P>

Vous voudrez peut-être explorer le variable de condition approche de traiter avec plusieurs threads affectés par une seule condition partagée. Java a beaucoup d'outils de niveau supérieur pour cela dans le java.util.concurrent code> bibliothèque, mais il est bon de connaître les méthodes d'API plus anciennes de niveau inférieur, d'autant plus que vous travaillez directement avec thread code> instances déjà . p>

chaque Objet code> a attendre () code> et notifier () code> méthodes. L'objet code> représente la condition ou au moins le moniteur associé à celui-ci. La méthode wait () code> est appelée dans un tandis que code> boucle qui teste la condition et bloque le thread d'appel jusqu'à certains autres appels de threads notifier () code> . Ensuite, tous les fils d'attente seront éveillés et ils auront tous concurrencer la serrure et la possibilité de tester la condition à nouveau. Si la condition reste vraie à ce stade, tous les threads procéderont. P>

Voici ce que votre code ressemblerait à utiliser cette approche: p>

public class GuardedBlock {

    private boolean guard = false;

    private static void threadMessage(String message) {
        System.out.println(Thread.currentThread().getName() + ": " + message);
    }

    public static void main(String[] args) throws Exception {
        GuardedBlock guardedBlock = new GuardedBlock();

        Thread thread1 = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    synchronized (guardedBlock) {
                        guardedBlock.guard = true;
                        guardedBlock.notifyAll();
                    }
                    threadMessage("Set guard=true");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });

        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                threadMessage("Start waiting");
                while (!guardedBlock.guard) {
                    synchronized (guardedBlock) {
                        try {
                            guardedBlock.wait();
                        }
                        catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                threadMessage("Finally!");
            }
        });

        thread1.start();
        thread2.start();
        thread2.join();

        System.out.println("Done");
    }
}
  • verrouillage doit être pris chaque fois que la condition est lue ou écrite (via synchronisé code> mot-clé.) li>
  • Guard CODE> La condition est testée à l'intérieur d'un pendant que code> boucle, mais cette boucle bloque pendant le attendre () code> appel. La seule raison pour laquelle il reste un pendant que code> est de gérer des situations dans lesquelles de nombreux threads et la condition change de plusieurs reprises. Ensuite, la condition doit être réutilisée lorsqu'un thread est réveillé, dans le cas où un autre fil modifie la condition dans le minuscule écart entre le réveil et la reprise de la serrure. Li>
  • Les threads en attente sont notifiés lorsque Condition code> est défini sur true code> (via notifier () code> appels.) Li>
  • blocs de programme sur l'instance code> thread2 code> à la très fin, de sorte que nous ne quittant pas le fil principal avant toutes les fils Terminer (via le () code> appel.) li> ul>

    Si vous regardez l'objet code> code> API, vous verrez qu'il y a aussi un notifier () code> méthode. Il est plus simple d'utiliser toujours notifier () code>, mais si vous souhaitez comprendre la différence entre ces deux méthodes, Voir ceci alors post . p> p>


0 commentaires