2010-03-28 30 views
26

¿Qué tan seguro es el thread enum en java? Estoy implementando un Singleton usando enum (según Java Efectivo de Bloch), ¿debería preocuparme en absoluto por la seguridad de los hilos de mi singleton enum? ¿Hay alguna forma de probar o refutar que es seguro para subprocesos?¿Qué tan seguro es el thread enum en java?

// Enum singleton - the preferred approach 
public enum Elvis { 
    INSTANCE; 
    public void leaveTheBuilding() { ... } 
} 

Gracias

+0

¿Qué quiere decir con "thread-safe"? El método 'leaveTheBuilding' no está sincronizado, por lo que, por supuesto, podría ser ejecutado por múltiples hilos simultáneamente. ¿O estás hablando de inicializar el 'INSTANCE'? –

+1

Cuando dice "singleton", ¿quiere decir que tiene estado mutable? En ese caso, estás en un perdedor de todos modos. –

+0

Solo mi $ 0.02, pero creo que usar una enumeración para hacer cumplir el patrón de Singleton es una ofuscación de código. Dicho esto, sé que Bloch junto con muchos otros lo sugieren y mi comentario puede ser el ladrido de un dinosaurio. –

Respuesta

8

Esta técnica es absolutamente seguro para subprocesos. Se garantiza que un valor enum solo se inicialice una vez, cada vez, con un solo hilo, antes de ser utilizado. Sin embargo, no estoy seguro de si es cuando se carga la clase enum o la primera vez que se accede al valor enum. Usar esta técnica es en realidad un poco más seguro que otras técnicas, porque ni siquiera hay una forma de reflexión para obtener una segunda copia de su singleton basado en enum.

+1

Si la enumeración hace algo realmente estúpido en su intializador estático (o en otro lugar), teóricamente podría ser un problema. (La inicialización estática es una fase diferente de la carga de la clase - vea el argumento 3 'Class.forName'.) –

+0

sí, traté de encontrar una forma reflexiva para iniciar otra enumeración, y no pude. – portoalet

+0

@Mike ¿Cómo puedo saber cuándo se inicializa el valor enum, es decir, cuándo se carga la clase enum o la primera vez que se accede al valor enum? – portoalet

31

Como dice @Mike, la creación de enum garantiza la seguridad de los subprocesos. Sin embargo, los métodos que agregue a una clase enum no conllevan ninguna garantía de seguridad de hilos. En particular, el método leaveTheBuilding se puede ejecutar, concurrentemente, por múltiples hilos. Si este método tiene efectos secundarios (cambia el estado de alguna variable), entonces debe pensar en protegerlo (es decir, hacerlo synchronized) o partes de los mismos.

9

Enum personalizada La definición puede no ser segura para subprocesos. Por ejemplo,

RoleEnum.java:

package com.threadsafe.bad; 

public enum RoleEnum { 
     ADMIN(1), 
     DEV(2), 
     HEAD(3); 

     private Integer value; 
     private RoleEnum(Integer role){ 
       this.value=role;   
     } 
     public static RoleEnum fromIntegerValue(Integer role){ 

       for(RoleEnum x : values()){ 
        if(x.value == role){ 
          return x; 
        } 
       } 
       return RoleEnum.HEAD;    
     } 

     Class<?> buildFromClass; 
     public void setBuildFromClass(Class<?> classType){ 
       buildFromClass=classType; 
     } 
     public Class<?> getBuildFromClass(){ 
       return this.buildFromClass; 
     } 
} 

Main.java:

package com.threadsafe.bad; 

public class Main { 

     public static void main(String[] args) { 
       // TODO Auto-generated method stub 

       Thread threadA = new Thread(){ 
        public void run(){ 
          System.out.println("A started"); 
          RoleEnum role; 
          role=RoleEnum.fromIntegerValue(1); 
          System.out.println("A called fromIntegerValue"); 
          role.setBuildFromClass(String.class); 
          System.out.println("A called setBuildFromClass and start to sleep"); 


          try { 
            Thread.sleep(10000); 
          } catch (InterruptedException e) { 
            // TODO Auto-generated catch block 
            e.printStackTrace(); 
          } 
          System.out.println("Thread A: "+role.getBuildFromClass()); 
        } 
       }; 

       Thread threadB = new Thread(){ 
        public void run(){ 
          System.out.println("B started"); 
          RoleEnum role; 
          role=RoleEnum.fromIntegerValue(1); 
          role.setBuildFromClass(Integer.class); 
          System.out.println("B called fromIntegerValue&setBuildFromClass and Start to sleep"); 
          try { 
            Thread.sleep(20000); 
          } catch (InterruptedException e) { 
            // TODO Auto-generated catch block 
            e.printStackTrace(); 
          } 
          System.out.println("B waked up!"); 

          System.out.println("Thread B: "+ role.getBuildFromClass()); 
        } 

       }; 

       threadA.start(); 
       threadB.start(); 


     } 

} 

veces, la salida será:

B comenzó

B llama fromIntegerValue & conjunto BuildFromClass y empezar a dormir

Un comenzó

Un llamados fromIntegerValue

Un llamados setBuildFromClass y empezar a dormir

Tema A: java.lang.String clase

B despertado!

Subproceso B: clase java.lang.String < -Esperamos java.lang.Entero

veces, la salida será:

Un comenzó

Un llamado fromIntegerValue

Un llamado setBuildFromClass y empezar a dormir

B comenzó

B llamada fromIntegerValue & setBuildFromClass y empezar a dormir

Tema A: clase java.lang.Integer < -Esperamos java.lang.String

B despertado!

Tema B: clase java.lang.Integer

1

Adición sincronizado evita estado inconsistente con enumeraciones.

El código que se muestra a continuación se bloqueará siempre y se imprimirá "Uno". Sin embargo, cuando comente sincronizado, también se imprimirán otros valores.

import java.util.Random; 
import java.util.concurrent.atomic.AtomicInteger; 

public class TestEnum 
{ 
    public static AtomicInteger count = new AtomicInteger(1); 

    public static enum E 
    { 
     One("One"), 
     Two("Two"); 

     String s; 

     E(final String s) 
     { 
      this.s = s; 
     } 

     public void set(final String s) 
     { 
      this.s = s; 
     } 

     public String get() 
     { 
      return this.s; 
     } 
    } 

    public static void main(final String[] args) 
    { 
     doit().start(); 
     doit().start(); 
     doit().start(); 
    } 

    static Thread doit() 
    { 
     return new Thread() 
     { 
      @Override 
      public void run() 
      { 
       String name = "MyThread_" + count.getAndIncrement(); 

       System.out.println(name + " started"); 

       try 
       { 
        int i = 100; 
        while (--i >= 0) 
        { 

         synchronized (E.One) 
         { 
          System.out.println(E.One.get()); 
          E.One.set("A"); 
          Thread.sleep(new Random().nextInt(100)); 
          E.One.set("B"); 
          Thread.sleep(new Random().nextInt(100)); 
          E.One.set("C"); 
          Thread.sleep(new Random().nextInt(100)); 
          E.One.set("One"); 
          System.out.println(E.One.get()); 
         } 

        } 
       } 
       catch (InterruptedException e) 
       { 
        // TODO Auto-generated catch block 
        e.printStackTrace(); 
       } 

       System.out.println(name + " ended"); 
      } 
     }; 
    } 
} 
Cuestiones relacionadas