394 votes

Évitez synchronisé (ceci) en Java?

Chaque fois qu'une question surgit sur DONC sur Java synchronisation, certaines personnes sont très désireux de souligner que, synchronized(this) doit être évitée. Au lieu de cela, disent-ils, un verrou sur un domaine privé de référence est à privilégier.

Certaines des raisons sont les suivantes:

D'autres personnes, moi y compris, soutiennent que l' synchronized(this) est un idiome qui est beaucoup utilisé (également dans les bibliothèques Java), est sûr et bien compris. Il ne devrait pas être évitée parce que vous avez un bug, et que vous n'avez pas la moindre idée de ce qui se passe dans votre programme multithread. En d'autres termes: s'il est applicable, de l'utiliser ensuite.

Je suis intéressé à voir quelques exemples du monde réel (pas de blabla) où évitant un verrou sur l' this est préférable lors de l' synchronized(this) serait aussi faire le travail.

Donc: faut-il toujours d'éviter synchronized(this) et de le remplacer par un verrou sur un domaine privé de référence?


Certains plus d'infos (mise à jour des réponses):

  • nous parlons d'instance de synchronisation
  • les deux implicite (synchronisé méthodes) et de la forme explicite de l' synchronized(this) sont considérés comme
  • si vous citez Bloch ou d'autres autorités sur le sujet, ne laissez pas les parties que vous n'aimez pas (par exemple, Efficace Java, le point sur la Sécurité des Threads: "Généralement, c'est le verrouillage de l'instance elle-même, mais il y a des exceptions.")
  • si vous avez besoin de précision dans votre verrouillage d'autres qu' synchronized(this) offre, alors synchronized(this) n'est pas applicable, ce qui n'est pas la question

133voto

Darron Points 13196

Je vais couvrir chaque point séparément.

  1. Un mauvais code peut voler votre serrure (très populaire cet, a aussi un "accidentellement" variante)

    Je suis plus inquiet par hasard. Ce que ça donne, c'est que cette utilisation de ce est une partie de votre classe exposés interface, et doit être documentée. Parfois, la capacité du code à utiliser votre serrure est souhaitée. Cela est vrai des choses comme des Collections.synchronizedMap (voir la javadoc).

  2. Tous synchronisés méthodes au sein de la même classe d'utiliser exactement la même serrure, ce qui réduit le débit

    C'est trop simpliste de la pensée; il suffit de se débarrasser de la synchronisation(ce) ne résoudra pas le problème. Une synchronisation correcte pour le débit de prendre plus de pensée.

  3. Vous êtes (inutilement) de l'exposer trop d'informations

    C'est une variante de #1. Utilisation de la synchronisation(ce) est la partie de votre interface. Si vous ne voulez pas/besoin de cet exposé, ne pas le faire.

89voto

cletus Points 276888

Eh bien, tout d'abord, il convient de rappeler que:

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

est sémantiquement équivalent à:

public synchronized void blah() {
  // do stuff
}

qui est une raison de ne pas utiliser synchronized(this). Vous pourriez dire que vous pouvez faire des trucs autour de la synchronized(this) bloc. La raison habituelle est d'essayer et d'éviter d'avoir à faire synchronisée de vérifier à tous, ce qui conduit à toutes sortes de problèmes de concurrence d'accès, plus précisément le double vérification-problème de verrouillage, qui va juste pour montrer combien il peut être difficile de faire un relativement simple vérification des threads.

Privé de serrure est un mécanisme de défense, qui n'est jamais une mauvaise idée.

Aussi, comme vous l'avez fait allusion, privé verrous de contrôle de la granularité. Un ensemble d'opérations sur un objet peut être totalement étranger à l'autre, mais synchronized(this) va s'excluent l'accès à tous.

synchronized(this) seulement n'a pas vraiment vous donner quoi que ce soit.

58voto

Andreas Bakurov Points 857

Lorsque vous utilisez la synchronisation(ce) vous utilisez l'instance de classe comme une serrure elle-même. Cela signifie que si un verrou est acquis par le thread 1 thread 2 doit attendre

Supposons que le code suivant

public void method1() {
    do something ...
    synchronized(this) {
        a ++;      
    }
    ................
}


public void method2() {
    do something ...
    synchronized(this) {
        b ++;      
    }
    ................
}

Méthode 1 modification de la variable a et la méthode 2 modification de la variable b, les modifications simultanées de la même variable par deux fils doivent être évités et il est. MAIS alors que thread1 modifiant un et thread2 modifiant b elle peut être réalisée sans aucune condition de course.

Malheureusement, le code ci-dessus ne permettra pas que cela puisque nous sommes en utilisant la même référence, pour une serrure, Ce qui signifie que les fils, même s'ils ne sont pas en condition de course devrait attendre et, évidemment, le code des sacrifices de la simultanéité du programme.

La solution est d'utiliser 2 serrures différentes pour les deux variables différentes.

  class Test {
        private Object lockA = new Object();
        private Object lockB = new Object();

public void method1() {
    do something ...
    synchronized(lockA) {
        a ++;      
    }
    ................
}


public void method2() {
    do something ...
    synchronized(lockB) {
        b ++;      
    }
    ................
 }

L'exemple ci-dessus utilise plus fine des serrures (2 verrous au lieu d'un (lockA et lockB pour les variables a et b , respectivement), et comme un résultat permettant de mieux simultanéité, d'autre part, il est devenu plus complexe que le premier exemple ...

16voto

Olivier Points 978

Alors que je suis d'accord de ne pas adhérer aveuglément à dogmatique des règles, le "verrou de voler" scénario semble tellement excentrique pour vous? Un thread peut en effet acquérir le verrou sur votre objet "externe"(synchronized(theObject) {...}), en bloquant les autres threads en attente sur synchronisée des méthodes d'instance.

Si vous ne croyez pas dans le code malveillant, considèrent que ce code pourrait provenir de tiers (par exemple, si vous développez une sorte de serveur d'application).

Le "accidentelle" version semble moins probable, mais comme ils le disent, "faire quelque chose d'idiot-proof et quelqu'un va inventer une meilleure idiot".

Je suis d'accord avec l'il-dépend-sur-ce-que-la-classe-ne école de pensée.


Modifier les suivantes eljenso des 3 premiers commentaires:

Je n'ai jamais connu la serrure de voler problème mais ici, c'est un scénario fictif:

Disons que votre système est un conteneur de servlets, et l'objet que nous envisageons est l' ServletContext mise en œuvre. Ses getAttribute méthode doit être thread-safe, dans le contexte des attributs de données partagées; donc vous déclarer comme synchronized. Nous allons imaginer que vous disposer d'un service d'hébergement basée sur votre récipient de mise en œuvre.

Je suis votre client et de déployer mon "bon" servlet sur votre site. Il se trouve que mon code contient un appel à l' getAttribute.

Un hacker, déguisé comme un autre client, déploie son malveillants servlet sur votre site. Il contient le code suivant dans l' init méthode:

synchronisée (ce.getServletConfig().getServletContext()) {
 while (true) {}
}

En supposant que nous partageons le même contexte de servlet (autorisé par la spec, aussi longtemps que les deux servlets sont sur le même hôte virtuel), mon appel sur getAttribute est fermé pour toujours. Le pirate informatique a réalisé un DoS sur mon servlet.

Cette attaque n'est pas possible si l' getAttribute est synchronisé sur un domaine privé de verrouillage, en raison de la 3e partie du code ne peut pas acquérir ce verrou.

J'avoue que l'exemple est conçu et une vue simpliste de la façon dont un conteneur de servlet œuvres, mais à mon humble avis, il le prouve.

Donc, je voudrais faire mon choix de conception basée sur la considération de la sécurité: vais-je avoir un contrôle complet sur le code qui a accès aux instances? Quelle serait la conséquence d'un thread est un verrou sur une instance indéfiniment?

12voto

serg10 Points 10157

Il semble qu'il y un autre consensus dans le C# et Java camps sur. La majorité du code Java, j'ai vu les utilisations:

// apply mutex to this instance
synchronized(this) {
    // do work here
}

alors que la majorité de code C# opte pour le sans doute plus sûr:

// instance level lock object
private readonly object _syncObj = new object();

...

// apply mutex to private instance level field (a System.Object usually)
lock(_syncObj)
{
    // do work here
}

Le C# langage est certainement plus sûr. Comme mentionné précédemment, aucun malveillants / l'accès accidentel à la serrure peut être faite à partir de l'extérieur de l'instance. Code Java a ce risque de trop, mais il semble que la communauté Java a évolué au fil du temps à un peu moins fort, mais un peu plus laconique version.

Ce n'est pas conçue comme une pique à l'encontre de Java, juste un reflet de mon expérience de travail sur les deux langues.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X