2008-12-27 12 views
47

Me encontré con este article discutiendo por qué el paradigma de bloqueo de doble comprobación se rompe en Java. ¿El paradigma es válido para .NET (en particular, C#), si las variables se declaran volatile?Bloqueo verificado en .NET

Respuesta

23

Implementing the Singleton Pattern in C# habla sobre este problema en la tercera versión.

Dice:

Hacer la instancia de variable volátil puede hacer que funcione, como lo haría barrera de memoria explícita llama, aunque en este último caso, incluso los expertos no se ponen de acuerdo exactamente lo que se requieren barreras. ¡Tiendo a tratar de evitar situaciones en las que los expertos no están de acuerdo con lo que está bien y lo que está mal!

El autor parece dar a entender que el doble bloqueo tiene menos probabilidades de funcionar que otras estrategias y, por lo tanto, no debe utilizarse.

+0

La nueva ubicación del artículo es: http://csharpindepth.com/Articles/General/Singleton.aspx – dotnetguy

7

Tenga en cuenta que en Java (y muy probablemente en .Net también), el bloqueo con doble comprobación para la inicialización de singleton es completamente innecesario y está roto. Como las clases no se inicializan hasta que se usan por primera vez, la inicialización lenta deseada ya se logra con esto;

private static Singleton instance = new Singleton(); 

A menos que su clase Singleton contiene cosas como constantes que se pueda acceder antes de que se utiliza una instancia de Singleton principio, esto es todo lo que necesita hacer.

+1

DCL funciona desde Java 5 (ver el comentario de Jon Skeet, aunque no habló sobre exactamente qué debes hacer para que funcione). Necesita: 1. Java 5. 2. La referencia DCL declarada volátil (o atómica de alguna manera, por ejemplo, utilizando AtomicReference). –

+1

Consulte la sección "Debajo del nuevo modelo de memoria de Java" en http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html –

+0

Para los patrones de Java Singleton y DCL, consulte este enlace http: // blogs .sun.com/cwebster/entry/double_check_locking –

67

El bloqueo de doble comprobación ahora funciona tanto en Java como en C# (el modelo de memoria de Java cambió y este es uno de los efectos). Sin embargo, debe obtenerlo exactamente derecha. Si ensucias las cosas aunque sea levemente, puedes terminar perdiendo la seguridad del hilo.

Como han dicho otras respuestas, si está implementando el singleton pattern hay formas mucho mejores de hacerlo. Personalmente, si estoy en una situación en la que tengo que elegir entre el bloqueo doblemente comprobado y el código de "bloqueo cada vez", me iría por el bloqueo cada vez hasta que tuviera evidencia real de que estaba causando un cuello de botella. Cuando se trata de enhebrar, un patrón simple y obviamente correcto vale mucho.

+1

@Jon: El artículo de la pregunta afirma que 'volátil 'ayuda a JDK5 +. ¿Qué hay de .NET? ¿Fue 'volátil 'suficiente para implementar un bloqueo de doble verificación correcto en .NET 1.1? (por ejemplo, ¿qué pasa con el ejemplo "Tercera versión - intento de seguridad de subprocesos mediante el bloqueo de doble comprobación" en su artículo: ¿técnicamente es "fijo" cuando 'volátil 'se pone en el campo estático' instancia'?) – IgorK

+9

@IgorK: sí , I * cree * que funciona cuando la variable está marcada como volátil. –

+0

@Jon: OK, gracias. Al menos para singletons tenemos mejores soluciones de hecho. – IgorK

20

.NET 4.0 tiene un nuevo tipo: Lazy<T> que quita cualquier preocupación acerca de obtener el patrón incorrecto. Es parte de la nueva Task Parallel Library.

Véase el MSDN Parallel Computing Center Dev: http://msdn.microsoft.com/en-us/concurrency/default.aspx

Por cierto, hay una backport (creo que no es compatible) para .NET 3.5 SP1 disponible here.

-5

He conseguido una doble comprobación de bloqueo a trabajar mediante el uso de un valor booleano (es decir, utilizando una primitiva para evitar la inicialización perezoso):

private static Singleton instance; 
private static boolean created; 
public static Singleton getInstance() { 
    if (!created) { 
     synchronized (Singleton.class) { 
      if (!created) { 
       instance = new Singleton(); 
       created = true; 
      } 
     } 
    } 
    return instance; 
} 
+2

¿Cómo has demostrado que funciona? – erikkallen

+0

El problema con el doble bloqueo comprobado es que la instancia podría ser no nula pero aún no inicializada. Por lo tanto, un hilo fuera del bloque sincronizado podría pasar la comprobación nula inicial (no sincronizada) aunque el objeto aún no se haya inicializado. El código anterior lo soluciona al verificar el booleano. Cuando create = true es hit, sabemos que la instancia se ha inicializado, por lo que no hay forma de que un hilo fuera del bloque sincronizado pueda obtener una instancia no inicializada. – Jono

2

He conseguido una doble comprobación de bloqueo de trabajar por utilizando un booleano (es decir, utilizando una primitiva para evitar la inicialización diferida):

El singleton que utiliza booleano no funciona. El orden de las operaciones como se ve entre los diferentes hilos no está garantizado a menos que atraviese una barrera de memoria. En otras palabras, como se ve desde un segundo hilo, created = true pueden ser ejecutados antes de instance= new Singleton();

0

No acabo de entender por qué hay un montón de patrones de implementación de bloqueo doble comprobación (aparentemente para evitar la idiosincrasia del compilador en varios idiomas). El artículo de Wikipedia sobre este tema muestra el método ingenuo y las posibles maneras de resolver el problema, pero ninguno es tan simple como esto (en C#):

public class Foo 
{ 
    static Foo _singleton = null; 
    static object _singletonLock = new object(); 

    public static Foo Singleton 
    { 
    get 
    { 
     if (_singleton == null) 
     lock (_singletonLock) 
      if (_singleton == null) 
      { 
      Foo foo = new Foo(); 

      // Do possibly lengthy initialization, 
      // but make sure the initialization 
      // chain doesn't invoke Foo.Singleton. 
      foo.Initialize(); 

      // _singleton remains null until 
      // object construction is done. 
      _singleton = foo; 
      } 
     return _singleton; 
    } 
    } 

En Java, se haría uso sincronizado() en lugar de lock(), pero básicamente es la misma idea. Si existe la posible incoherencia de cuándo se asigna el campo singleton, ¿por qué no utilizar primero una variable de ámbito local y luego asignar el campo singleton en el último momento posible antes de salir de la sección crítica? ¿Me estoy perdiendo de algo?

Existe el argumento de @michael-borgwardt que en C# y Java el campo estático solo se inicializa una vez en el primer uso, pero que el comportamiento es específico del idioma. Y he usado este patrón frecuentemente para la inicialización lenta de una propiedad de colección (por ejemplo, user.Sessions).

+1

El error en el análisis es el siguiente: "' _singleton' permanece 'nulo' hasta que se completa la construcción del objeto." El compilador puede reorganizar toda esta línea por encima de 'foo.Initialize()', o incluso optimizar el 'foo' variable por completo Incluso si un compilador es tonto, la CPU es inteligente y puede ejecutar algunas memorias almacenadas dentro de 'foo.Initialize()' * después de * la asignación '_singleton = foo'. El artículo vinculado desde la publicación original también explica otras razones por las que este código no hace lo que crees que hace. – kkm

0

No entiendo por qué todas las personas dicen que el doble bloqueo es un mal patrón, pero no adaptan el código para que funcione correctamente. En mi opinión, este código a continuación debería funcionar bien.

Si alguien pudiera decirme si este código sufre el problema mencionado en el artículo de Cameron, hágalo.

public sealed class Singleton { 
    static Singleton instance = null; 
    static readonly object padlock = new object(); 

    Singleton() { 
    } 

    public static Singleton Instance { 
     get { 
      if (instance != null) { 
       return instance; 
      } 

      lock (padlock) { 
       if (instance != null) { 
        return instance; 
       } 

       tempInstance = new Singleton(); 

       // initialize the object with data 

       instance = tempInstance; 
      } 
      return instance; 
     } 
    } 
} 
+2

La razón por la que las personas dicen que es malo es porque es increíblemente fácil equivocarse. Creo que tu código necesita un '' 'volátil''' en el miembro' '' instance'''. (Si el código funciona es, al menos en este caso, no una cuestión de opinión, sino una cuestión de hecho. Creo que el hecho es que no funciona, pero podría estar equivocado en esto). – erikkallen

+6

@erikkallen No es necesario que volátil en .NET. No olvide que .NET tiene un modelo de memoria más fuerte que Java, eso es lo que confunde mucho a la gente. La instrucción 'lock' en C# (' Monitor.Enter() ',' Monitor.Exit() ') crea implícitamente una valla de memoria completa (http://www.albahari.com/threading/part4.aspx). La única excusa para usar la volatilidad en C# por parte de algunos es porque el bloqueo con doble verificación tiene el mismo aspecto en Java y en C#, por lo que no confunde a las personas. Mi opinión es usar 'Lazy ' (https://msdn.microsoft.com/en-us/library/dd997286(v=vs.110).aspx) – Tiny

Cuestiones relacionadas