2011-02-02 11 views
55

Según the Java Language Specification, los constructores no se pueden marcar sincronizados porque otros subprocesos no pueden ver el objeto que se está creando hasta que la hebra que lo creó haya terminado. Esto parece un poco extraño, porque de hecho puede tener otro hilo de ver el objeto mientras se está construyendo:¿Por qué no se pueden sincronizar los constructores de Java?

public class Test { 
    public Test() { 
     final Test me = this; 
     new Thread() { 
      @Override 
      public void run() { 
       // ... Reference 'me,' the object being constructed 
      } 
     }.start(); 
    } 
} 

sé que este es un ejemplo bastante artificial, pero parece que en la teoría de que alguien pueda llegar a una Caso más realista donde marcar al constructor sincronizado sería legítimo para evitar carreras con hilos como este.

Mi pregunta es esta: ¿hay alguna razón por la cual Java específicamente desaprobaría el modificador sincronizado en un constructor? Tal vez mi ejemplo anterior es defectuoso, o tal vez realmente no hay ninguna razón y es una decisión de diseño arbitraria. En cualquier caso, tengo mucha curiosidad y me encantaría saber la respuesta.

+9

Como comentario adicional, se recomienda no permitir que la referencia "this" escape antes de que el constructor haya finalizado. –

+1

@Mike Q- He escuchado esto antes, pero no entiendo completamente por qué. ¿Hay alguna razón en particular por qué? Pude ver que ocurrían cosas malas si le dabas una referencia antes de que terminaras de inicializar el objeto, pero ¿y si es lo último que haces en el constructor? – templatetypedef

+2

es realmente el tema de otra pregunta, pero incluso si es lo último que haces en el constructor, si el objeto está subclasificado, entonces la subclase no ha terminado de construirse. Si esta clase es final, y no encadenas constructores (llamando a esto (...)) y haces algo más después de la llamada en cadena y es lo último que haces, está bien. Excepto por supuesto que cualquiera de esas decisiones podría cambiar (podría agregar un segundo constructor más adelante). – Yishai

Respuesta

25

Si realmente necesita la sincronización del resto del constructor contra cualquier hilo, que de todos modos obtiene una referencia a su objeto construido que aún no es totalmente-, se puede utilizar un bloque sincronizado:

public class Test { 
    public Test() { 
     final Test me = this; 
     synchronized(this) { 
      new Thread() { 
      @Override 
      public void run() { 
       // ... Reference 'me,' the object being constructed 
       synchronized(me) { 
        // do something dangerous with 'me'. 
       } 
      } 
      }.start(); 
      // do something dangerous with this 
     } 
    } 
} 

Por lo general, se considera un mal estilo "dar" a su objeto aún no construido de esta manera, por lo que un constructor sincronizado no es necesario.


En algunos casos de esquina sería útil un constructor sincronizado. Aquí es un ejemplo más realista, a partir de la discusión de la respuesta de Bozho:

public abstract class SuperClass { 

    public SuperClass() { 
     new Thread("evil") { public void run() { 
      doSomethingDangerous(); 
     }}).start(); 
     try { 
      Thread.sleep(5000); 
     } 
     catch(InterruptedException ex) { /* ignore */ } 
    } 

    public abstract void doSomethingDangerous(); 

} 

public class SubClass extends SuperClass { 
    int number; 
    public SubClass() { 
     super(); 
     number = 2; 
    } 

    public synchronized void doSomethingDangerous() { 
     if(number == 2) { 
      System.out.println("everything OK"); 
     } 
     else { 
      System.out.println("we have a problem."); 
     } 
    } 

} 

Queremos que el método doSomethingDangerous() sólo se le llama después de la construcción de nuestro objeto de subclase es completa, por ejemplo, solo queremos la salida "todo OK". Pero en este caso, cuando solo puede editar su SubClass, no tiene posibilidad de lograr esto. Si el constructor se pudiera sincronizar, resolvería el problema.

Entonces, lo que aprendemos al respecto: nunca haga algo como lo hice aquí en el constructor de la superclase, si su clase no es definitiva, y no llame a ningún método no final de su propia clase desde su constructor.

+0

ahh, ven conmigo :) +1 –

+1

@Paulo, tu ejemplo es muy artificial. En primer lugar, sería una mala práctica llamar a un método abstracto de un constructor, pero lo más importante, si no hubiera subprocesos, la subclase tendría * exactamente * el mismo problema. Entonces sincronizado agregaría qué? Soporte para un caso altamente improbable al tiempo que hace que la subclase tenga que trabajar de todos modos en el caso mucho más común de una manera que podría haber funcionado en ambos casos. – Yishai

+0

@Yishai: Tiene razón, el ejemplo no es tan realista. Pero he visto clases que publican sus propios objetos en el constructor a otros hilos (no necesariamente en la misma clase, y no necesariamente con un reposo), y luego puede tener tales problemas donde podría ayudar un constructor sincronizado. Por supuesto, este método en mi ejemplo debe protegerse para ser llamado desde el constructor de la superclase. –

7

Porque synchronized garantiza que las acciones en los mismos objetos no se deben realizar por varios hilos. Y cuando se llama al constructor, todavía no tiene el objeto. Es lógicamente imposible que dos hilos accedan al constructor del mismo objeto.

En su ejemplo, incluso si el nuevo hilo invoca un método, ya no se trata del constructor, sino del método de destino que se sincroniza o no.

+0

Tiene razón en que dos subprocesos no pueden llamar al constructor, pero ¿qué ocurre si un subproceso llama al constructor, que luego desencadena otro subproceso que intenta invocar un método sincronizado diferente de la clase? En ese caso, tener un constructor sincronizado evitaría que el segundo hilo invoque cualquier método sincronizado hasta que el constructor finalice. – templatetypedef

+1

ya no se trata del constructor, sino del método en cuestión. – Bozho

+3

Ellos pueden hacer un sincronizado (esto) {} dentro de su constructor. Sincronizado en el propio constructor es una tontería. ¿Cómo se adquiere el mutex a un objeto antes de comenzar el proceso de llevarlo a la existencia? – Affe

5

En su ejemplo, el constructor solo se llama una vez desde un hilo.

Sí, es posible obtener una referencia a un Objeto incompleto (algunas discusiones sobre el bloqueo de doble comprobación y por qué está roto revelan este problema), sin embargo, no llamando al constructor por segunda vez.

Sincronizado en el constructor evitaría que dos hilos llamaran al constructor en el mismo objeto simultáneamente, y eso no es posible, ya que nunca es posible llamar al constructor en una instancia de objeto dos veces, punto.

+0

Downvoter, ¿por qué? – Yishai

+4

No he votado negativamente, pero una posible razón: sincronizado en el constructor no solo evitaría dos hilos llamando al constructor del mismo hilo (lo cual es imposible de todos modos), sino que también evitaría llamar a un método sincronizado de este objeto (o ingresar un sincronizado bloquear con este objeto como parámetro) en otro hilo. –

1

Veo pocas razones para prohibir que los constructores estén sincronizados.Sería útil en muchos escenarios en aplicaciones de subprocesos múltiples. Si entiendo correctamente el Modelo de Memoria Java (leí http://jeremymanson.blogspot.se/2008/11/what-volatile-means-in-java.html), la siguiente clase simple podría haberse beneficiado de un constructor sincronizado.

public class A { 
    private int myInt; 

    public /*synchronized*/ A() { 
     myInt = 3; 
    } 

    public synchronized void print() { 
     System.out.println(myInt); 
    } 
} 

En teoría, creo que una llamada a print()podía de impresión "0". Esto podría suceder si el Subproceso 1 crea una instancia de A, la referencia a la instancia se comparte con el Subproceso 2 y el Subproceso 2 llama al print(). Si no hay una sincronización especial entre la escritura myInt = 3 del Subproceso 1 y la lectura del mismo campo por el Subproceso 2, el Subproceso 2 no se garantiza que vea la escritura.

Un constructor sincronizado solucionaría este problema. ¿Estoy en lo cierto acerca de esto?

+0

(i) el problema que describes es que la instancia de A no se ha publicado correctamente, en cuyo caso su estado observado podría ser cualquier cosa (ii) podrías escribir 'synchronized (this) {myInt = 3; } 'en su constructor para resolver el problema, o marque' myInt' como final. – assylias

+0

También puede marcar la variable 'myInt' con la palabra clave' volátil'. – ponkin

+1

No necesariamente. Si la referencia del objeto se filtra a algún otro subproceso antes de que esté completamente construido, el otro subproceso también puede llamar al método no sincronizado de este objeto, en cuyo caso la sincronización del constructor nunca ayuda. Evitar el caso posterior significa que no se debe practicar ninguna fuga, a partir de entonces la sincronización del constructor se vuelve innecesaria. – lcn

0

El siguiente código puede lograr el resultado esperado para el constructor sincronizado.

public class SynchronisedConstructor implements Runnable { 

    private int myInt; 

    /*synchronized*/ static { 
     System.out.println("Within static block"); 
    } 

    public SynchronisedConstructor(){ 
     super(); 
     synchronized(this){ 
      System.out.println("Within sync block in constructor"); 
      myInt = 3; 
     } 
    } 

    @Override 
    public void run() { 
     print(); 
    } 

    public synchronized void print() { 
     System.out.println(Thread.currentThread().getName()); 
     System.out.println(myInt); 
    } 

    public static void main(String[] args) { 

     SynchronisedConstructor sc = new SynchronisedConstructor(); 

     Thread t1 = new Thread(sc); 
     t1.setName("t1"); 
     Thread t2 = new Thread(sc); 
     t2.setName("t2"); 

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

La cuestión se ha planteado en una lista de discusión utilizado por los escritores de la API de Java concurrentes y el modelo de memoria de Java. Se dan varias respuestas, en particular, Hans Boehm replied:

Algunos de nosotros (me incluyo IIRC) en realidad sostenido durante las deliberaciones Java modelo de memoria que los constructores deben ser sincronizados permitidos. Ahora podría ir en cualquier dirección. El código del cliente no debe usar razas para comunicar la referencia, por lo que no debería importar. Pero si no confías en los clientes de [tu clase], creo que los constructores sincronizados podrían ser útiles. Y esa fue una gran parte del razonamiento detrás de la semántica final del campo. [...] Como dijo David, puedes usar bloques sincronizados.

3

Constructor Modifiers section en JLS dice claramente

There is no practical need for a constructor to be synchronized, because it would 
lock the object under construction, which is normally not made available to other 
threads until all constructors for the object have completed their work. 

lo que no hay necesidad de constructor para ser sincronizado.

Además, no se recomienda dar a conocer la referencia de los objetos (esto) antes de crear el objeto. Una de las posibles situaciones ambiguas sería dar a conocer la referencia de los objetos es el constructor de la superclase cuando se está creando un objeto de clase.

+0

Podría sincronizar en otra cosa. – momomo

0

Tal sincronización podría tener sentido en algunos casos muy raros, pero supongo, no es sólo la pena:

  • siempre se puede utilizar un bloque sincronizado lugar
  • que sería compatible con la codificación en una manera bastante extraña
  • ¿Qué debería sincronizar? Un constructor es una especie de método estático, funciona en un objeto pero se llama sin él. ¡Así que sincronizar en la clase también tiene (algo) de sentido!

En caso de duda, déjalo fuera.

Cuestiones relacionadas