0
votes

Les verrous en multi threading doivent-ils toujours rester immuables?

J'essaie l'extrait de code ci-dessous qui donne le résultat attendu, mais je suis curieux de savoir combien d'objets de verrouillage je crée 1 ou 2?

package com.practice;

class A{
    String lock="";

    A(String lock){
        this.lock = lock;
    }

    A(){ }

    public  void printMe(int x) {
        int counter = x;
        synchronized (lock) {
            System.out.println(++counter);
        }
    }
}



public class MultiThreadingPractice {

    public static void main(String[] args) {
        A a = new A("1");

        Thread t1=new Thread(() -> {
                a.printMe(1);
        });

        a.lock = new String();

        Thread t2=new Thread(() -> {
                a.printMe(1);
        });

        t1.start();
        t2.start();
    }
}


6 commentaires

a.lock = new String () vraiment? Pourquoi?


Peut-être pourriez-vous expliquer ce que vous essayez réellement de faire? Le code que vous avez définit le verrou avant de démarrer l'un des threads, de sorte que seul le dernier verrou est utilisé.


Qu'entendez-vous par «toujours… immuable»? Vous demandez-vous si une variable qui fait référence à un objet de verrouillage doit être une variable final ? Demandez-vous si l'objet de verrouillage doit être un objet immuable? ou demandez-vous autre chose?


Un nouvel objet () n'est ni meilleur ni pire comme objet de verrouillage qu'un new String () ne le serait, mais si vous écrivez new Object () , vous aurez moins d'autres programmeurs qui se grattent la tête, essayant de comprendre quel genre de truc bizarre vous essayez de tirer.


@SolomonSlow J'essaie de comprendre s'il est toujours nécessaire d'avoir une clause finale attachée à l'objet de verrouillage.


@ Dip90 - c'est une question entièrement différente. Si le verrou est un objet utilisé pour synchronized (lock) alors non, il est garanti d'être libéré à la sortie de la clause synchronized. Peut-être pas si le fil devait être violemment tué, mais c'est une considération rare, et même dans ce cas, il n'y a pas de réponse automatique; déverrouiller une structure de données incohérente peut être pire que de la laisser verrouillée en permanence.


3 Réponses :


0
votes

curieux de savoir combien d'objets de verrouillage je crée 1 ou 2?

Cette ligne de votre programme crée une seule instance de String lorsque le programme est chargé, et vos deux instances A commencent par une référence au même String .

a.lock = new String();

Cette ligne crée une seconde instance de String , car new String (...) code > toujours crée un nouvel objet, indépendamment du fait qu'une autre chaîne avec la même valeur ait été ou non interné .

String lock="";

4 commentaires

Je ne vois qu'une seule instance A : A a = new A ("1") . Le verrou dans cette instance prend successivement 3 valeurs: une chaîne vide due à l'initialiseur, "1" via une affectation dans le constructeur et une chaîne vide en raison d'une réaffectation ultérieure.


@ un autre-dave D'Oh! Tu as raison. Il n'y a qu'une seule instance A . Concernant les "chaînes littérales", je ne me souviens pas si le langage nécessite que les littéraux de chaîne soient regroupés, mais je suis presque sûr qu'ils seront regroupés dans la plupart des implémentations JRE pratiques.


J'ai rétracté cette modification. Il existe deux objets String même si la valeur est la même séquence sous-jacente de caractères en mémoire et que la synchronisation est basée sur l'identité de l'objet String.


@SolomonSlow JLS 3.10.5



2
votes

Les verrous en multi threading doivent-ils toujours rester immuables? Oui.

Vous utilisez une chaîne (n'importe quel objet ferait l'affaire) comme verrou. Lorsque vous attribuez une nouvelle valeur (nouvelle chaîne) au verrou, cela signifie que vous disposez de plusieurs instances de verrou. Ce n'est pas grave tant que tous les threads se synchronisent sur la même instance de verrou, mais il n'y a rien de manifeste dans votre code de classe A pour garantir que c'est le cas.

Dans votre utilisation réelle dans l'exemple, vous êtes en sécurité. Étant donné qu'aucun thread ne démarre tant que vous n'avez pas terminé de définir le verrou sur la troisième et dernière instance, rien ne tentera de se synchroniser sur le verrou tant qu'il ne sera pas stable. (3 instances: la première est l'initialisation à la chaîne vide; la seconde est à l'argument constructeur fourni "1"; et la troisième est l'affectation explicite, à une chaîne vide différente). Donc, bien que ce code «fonctionne», il ne fonctionne que par ce que j'appelle «coïncidence», c'est-à-dire qu'il n'est pas thread-safe de par sa conception.

Mais supposons un cas où vous démarrez chaque thread immédiatement après l'avoir construit. Cela signifie que vous réaffecteriez le membre de verrouillage après l'exécution de t1, mais avant même la création de t2.

Après un certain temps, les deux threads seront synchronisés sur la nouvelle instance de verrou, mais pendant une période autour du moment où vous avez changé le verrou, le thread t1 pourrait être et est probablement dans le synchronized (lock) {.. .} en utilisant l'ancienne instance de verrou. Et à cette époque, le thread t2 pourrait s'exécuter et tenter de se synchroniser sur la nouvelle instance de verrou.

En bref, vous avez créé une fenêtre de chronométrage (risque de course) dans le mécanisme que vous comptez utiliser pour éliminer les fenêtres de chronométrage.

Vous pouvez organiser un niveau de synchronisation supplémentaire qui vous permet de remplacer le verrou, mais je ne peux pas imaginer une situation simple où cela serait nécessaire, utile ou raisonnable. Il est préférable d’allouer un verrou avant qu’un conflit ne se produise, puis de s’en tenir à ce verrou.

P.S. "Combien de serrures est-ce que je crée?" 3. Bien que les deux premiers ne soient jamais utilisés.


5 commentaires

Oh, tu as raison. Je n'ai pas remarqué que le t1.start () ne s'est produit qu'après le commutateur de verrouillage. OK, donc ce code est thread-safe dans la pratique, bien que très déroutant en raison d'un brassage de verrous inutile. Tout ce qui était nécessaire est la première déclaration et initialisation de String lock = "" .


J'ai changé ma réponse pour expliquer en quoi l'exemple réel est sûr, mais la conception n'est pas si bonne.


voté pour la première phrase et pour l'explication de la fenêtre de chronométrage. mais pas tout à fait d'accord avec "n'importe quel objet ferait l'affaire"; L'intention est probablement d'exprimer que tous les objets ont un verrou implicite, mais la possibilité d'interner ou de mettre en cache ne rend-elle pas les chaînes et les nombres plus difficiles à raisonner que d'autres objets, donc moins attrayants que les verrous?


@NathanHughes - Oui, je pense que vous avez raison. Toute implémentation qui a lock = String ("literal") est à risque de réutilisation inattendue des verrous, et il en va de même pour les classes de nombres Java. Mieux vaut l'éviter. Je recommanderais quand même un vieil objet ordinaire: une empreinte plus petite et le moins d'effets secondaires tels que les stages.


Non seulement cela, mais la variable de verrouillage doit être final pour éviter de vous tirer une balle dans le pied. Ainsi final Object lock = new Object () (une fois que vous avez décidé que les méthodes synchronisées ne feront pas pour vous, comme observé de manière appropriée par @AlexeiKaigorodov)



0
votes

Les verrous en multi threading doivent-ils toujours rester immuables? Non.

La manière typique de synchroniser est d'utiliser des méthodes synchronisées et non des verrous séparés. Cette approche est moins sujette aux erreurs et donc préférée. Cela équivaut à utiliser l'objet courant comme verrou:

synchronized(this) {
          ...
}

et puisque cet objet n'est généralement pas immuable, nous pouvons conclure que l'utilisation d'objets mutables comme verrous est une pratique courante.

Ce que vous faites dans votre code, lorsque vous modifiez la référence à l'objet de verrouillage (oui, vous changez la référence et non l'objet de verrouillage lui-même), est une très mauvaise approche . Cela provoque des erreurs de programmation.


3 commentaires

La valeur de ceci ne change jamais, comment la valeur de ceci pourrait-elle ne pas être immuable?


La raison pour laquelle les gens utilisent des verrous attribués explicitement est qu'ils peuvent avoir un verrouillage plus fin que «ceci». Je suppose que les gens qui en ont besoin devraient savoir ce qu'ils font. D'un autre côté, le code simple de l'OP ne semble pas en avoir besoin, du moins pas comme présenté ici. D'un autre côté, l'incrémentation d'un compteur semble être un travail pour AtomicInteger, pas «synchronisé».


@alexeikaigorodov - vous avez raison, nous avons lancé un peu «immuable». Puisque l'OP a utilisé une chaîne comme verrou, qui est un objet immuable, alors il ne fait évidemment jamais muter le verrou; il crée 3 verrous immuables différents. Mais la question à laquelle nous avons répondu était la question «évidente mais non énoncée» - la référence stockée dans le membre lock devrait-elle rester inchangée?