¿Por qué no se pueden sincronizar los constructores de Java?

De acuerdo con la Especificación del lenguaje Java , los constructores no se pueden marcar sincronizados porque otros hilos 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 puedo tener otro hilo para ver el objeto mientras se está construyendo:

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 en teoría parece que alguien podría llegar a un caso más realista en el que 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.

Si realmente necesita la sincronización del rest del constructor con cualquier subproceso que de alguna manera consiga una referencia a su objeto que aún no está totalmente construido, puede usar 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 para “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, un constructor sincronizado sería útil. Aquí hay un ejemplo más realista, 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() solo se llame después de que la construcción de nuestro objeto SubClass esté completa, por ejemplo, solo queremos el resultado “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 sobre esto: 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.

La pregunta se ha planteado en una lista de discusión utilizada por los redactores de la API concurrente de Java y el Modelo de memoria de Java. Se dieron varias respuestas, en particular, Hans Boehm respondió :

Algunos de nosotros (incluido yo IIRC) discutimos durante las deliberaciones del modelo de memoria de Java que deberían permitirse los constructores sincronizados. 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.

Porque synchronized garantías synchronized acciones en los mismos objetos no deben ser ejecutadas por múltiples 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.

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

Sí, es posible obtener una referencia a un objeto construido de forma incompleta (algunas discusiones sobre el locking 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.

La sección Modificadores de constructores 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. 

Por lo tanto, no es necesario sincronizar el constructor.

Tampoco 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.

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 de Java (leo 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() podría imprimir “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 las llamadas de Subproceso 2 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, no se garantiza que el Subproceso 2 vea la escritura.

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

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(); } } 

Tal sincronización podría tener sentido en algunos casos muy raros, pero supongo que simplemente no vale la pena:

  • siempre puedes usar un bloque sincronizado
  • sería compatible con la encoding de una manera bastante extraña
  • ¿En qué debería sincronizarse? 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.