2010-09-21 9 views
5

En este código, ¿por qué se ha escrito "No es seguro para subprocesos"? Gracias¿Por qué este código de construcción singleton NO es seguro para subprocesos?

class Singleton 
{ 
    private static Singleton _instance; 

    // Constructor is 'protected' 
    protected Singleton() 
    { 
    } 

    public static Singleton Instance() 
    { 
    // Uses lazy initialization. 
    // **Note: this is not thread safe.** 
    if (_instance == null) 
    { 
     _instance = new Singleton(); 
    } 

    return _instance; 
    } 
} 
+0

Idioma? La explicación será similar, pero no es lo mismo, entre Java y .NET. –

Respuesta

24

Si dos hilos corren el cheque if (_instance == null) al mismo tiempo, mientras que no hay ninguna instancia singleton creado, ambos intentan invocar new para crear la instancia Singleton y almacenar las referencias a ellos en la misma variable .

Dado que la referencia a la que intentarán almacenar se compartirá entre subprocesos, esta acción no será segura para subprocesos. También podría suceder que al crear dos instancias de la clase singleteone se rompa el programa.

+11

+1 pero podría valer la pena mencionar que ni siquiera tienen que ejecutarse al mismo tiempo. Un cambio de contexto en un solo procesador central ** después de ** Thread1 ha verificado la condición, pero ** antes ** asigna 'instance' haría que Thread2 cree Singleton. Cuando Thread1 vuelva a estar en juego, continuará donde lo dejó y también creará y asignará Singleton. –

+1

El cambio de contexto ni siquiera tiene que suceder en ese momento en particular. Con el modelo de memoria Java, el subproceso 2. aún podría ver el miembro de instancia como nulo, incluso si el subproceso 1 ha realizado la asignación. – nos

8

Porque Singleton no proporciona exclusión mutua sobre la propiedad _instance.

Use un candadopara lograr el hilo de seguridad:

Object thisLock = new Object(); 

public static Singleton Instance() 
{ 
    lock (thisLock) 
    { 
     if (_instance == null) 
     { 
      _instance = new Singleton(); 
     } 
    } 

    return _instance; 
} 

Este ejemplo es C# - no sé qué lenguaje de programación que está utilizando.

http://msdn.microsoft.com/en-us/library/c5kehkcz(VS.90).aspx

3

Debido a que es posible que múltiples instancias del Singleton se crea en este caso. Suponga que dos subprocesos han ingresado al método Instance y que el objeto singleton aún no se ha creado (es decir, _instance es NULL). Luego suponga que el primer subproceso ejecuta la condición if y lo ingresa. Pero antes de que ocurra se produce un cambio de contexto de subproceso new y el segundo subproceso comienza a ejecutarse. También prueba la condición if y descubre que es NULL y realiza un new. Ahora el primer subproceso comienza a ejecutarse y crea una instancia más del objeto.

+0

+1 tipo de lo que escribí en un comentario en este hilo (sin juego de palabras). Todas las respuestas son tan correctas, pero creo que esta respuesta es la más fácil de comprender. –

5

Sobre la base de la respuesta de RPM1984:

utilizo lo siguiente para el objeto de bloqueo: object thisLock = typeof(Sinlgeton);

o simplemente

... 
lock(typeof(Singleton)) 
{ 
    ... 
} 

para la Savy rendimiento entre vosotros:

public Singleton getInstance() 
{ 
    // the first query may save a performance-wise expensive lock - operation 
    if (null == _instance) 
    { 
     lock (typeof(Singleton)) 
     { 
      if (null == _instance) 
      { 
      _ instance = new Singleton() 
      } 
     } 
    } 

    return _instance; 
} 

BTW: Esto se llama dou patrón singleton bloqueado.

+0

Y el enlace [wikipedia] (http://en.wikipedia.org/wiki/Double-checked_locking). :) –

+2

-1: El bloqueo doble es bueno, pero nunca se debe bloquear el tipo de una clase. Un objeto estático local privado es el mejor. Compruebe la primera nota http://msdn.microsoft.com/en-us/library/system.type.aspx – aqwert

+1

¡Gracias por eso! Vives y aprendes ... ¡No estaba al tanto de esa nota! –

0

Creo que en Java es suficiente simplemente agregar un indicador sincronizado al método getInstance. Esto evitará que otros hilos entren en el método mientras que otro está dentro.

0

El problema más importante es que la comprobación de que una instancia no existe y su inicialización lenta no es una operación atómica.

El ejemplo más simple es que el subproceso A comprueba el condicional y cede el subproceso B; El hilo B verifica el mismo condicional y crea un nuevo objeto y devuelve ese nuevo objeto; El hilo B luego vuelve al hilo A, que continúa donde lo dejó; Luego, el hilo A va y crea un nuevo objeto y lo devuelve.

Hay algunos otros se pueden citar:

  1. La variable _singleton se debe marcar volátil para asegurar que las escrituras se publican correctamente.
  2. La clase debe marcarse como final o sellada para evitar problemas con las subclases.
  3. En Java, debe anular el método clone() para lanzar UnsupportedOperationException.
  4. Si por alguna razón hay un requisito para hacer que la clase sea serializable, también necesitaría proporcionar una implementación para manejar ese escenario (de lo contrario, un cliente podría continuamente deserializar una secuencia para crear varias instancias).
1

Esto resultó más interessting de lo que pensaba inicialmente:

Así que la mejor solución sería

public sealed class Singleton 
{ 
    private static readonly Singletion _instance = new Singleton(); 

    private Singleton() 
    { 
     //do your construction 
    } 

    public static Singleton getInstance() 
    { 
     return _instance; 
    } 
} 

Desde mi comprensión actual del entorno de programación (Java, .NET) no debe importar para esta solución.

¿Alguna opinión o comentario?

Otras lecturas que he excavado:

Editar: Como para Java también debería funcionar:

Ahora bien, si alguien señala una versión Guardar C++ sería completa ... (I lejos de C++ demasiado larga para recordar los detalles ...)

0

Si el singleton es barato para crear, y el requisito principal es que todo en la aplicación vea la misma, otro enfoque es:

 
    If TheSingleton Is Nothing Then 
    Dim newSingleton As New Singleton 
    If Interlocked.CompareExchange(TheSingleton, newSingleton, Nothing) IsNot Nothing Then 
     newSingleton.Dispose ' If applicable 
    End If 
    End If 

Si dos hilos pasan el "Si" prueba antes de que cualquiera que hace la CompareExchange, se crearán dos únicos. Sin embargo, solo el primer singleton que se pasará a CompareExchange se usará para cualquier cosa; el otro será descartado

0

En realidad, el ejemplo dado podría ser seguro. Además, incluso podría ser una idea razonablemente buena.

Si varios hilos alcanzan la comprobación nula antes de que el primer hilo haya escrito en _instance (y de hecho, quizás después de que el primer hilo haya escrito en _instance, pero antes de que la CPU del segundo hilo haya cargado el nuevo valor en su caché), luego, el segundo (y tercer, y cuarto ...) subproceso creará una nueva instancia y escribirá esto en _instance.

En un lenguaje recogido de basura, esto solo significa que, por un breve período, varios subprocesos tendrán su propia versión de _instance, pero muy pronto se saldrán del alcance y serán elegibles para la recolección de basura.

Ahora, esto es un desperdicio, pero si realmente es un problema o no depende de qué tan caro es crear una nueva instancia y si hay consecuencias negativas en tener más de una existencia. Muy a menudo la desventaja de tal duplicación innecesaria de objetos de instancia es bastante leve, en cuyo caso podría ser menos negativa que el costo de bloqueo (el bloqueo es relativamente barato pero no gratuito, esperar un bloqueo puede ser bastante caro, y en algunos casos [el punto muerto es el caso más extremo] invaluablemente caro), o incluso de CASsing. Incluso si es más costoso, todavía no puede ser inseguro.

Dado que no podemos juzgar si este es el caso en el ejemplo anterior, en realidad no sabemos si es seguro para subprocesos o no.

Lo más probable es que crear en una construcción estática sea el camino a seguir.

Cuestiones relacionadas