que predestroy method initialize component bean application java spring concurrency volatile

java - predestroy - spring initialize bean



¿Debo marcar los atributos de los objetos como volátiles si los inicio en @PostConstruct en Spring Framework? (3)

¿Debería preocuparme por la visibilidad de escritura de algunos datos en otros beans y marcarlos como volátiles?

No veo ninguna razón por la que no debas. Spring Framework no ofrece garantías adicionales de seguridad de subprocesos al llamar a @PostConstruct, por lo que aún pueden surgir problemas de visibilidad habituales. Un enfoque común sería declarar someData finales, pero si desea modificar el campo varias veces, obviamente no encajará.

Realmente no debería importar si es la primera escritura en el campo, o no. De acuerdo con el modelo de memoria Java, los problemas de reordenación / visibilidad se aplican en ambos casos. La única excepción se hace para los campos finales, que se pueden escribir de forma segura la primera vez, pero no se garantiza que las asignaciones posteriores (por ejemplo, a través de la reflexión) sean visibles.

volatile , sin embargo, puede garantizar la visibilidad necesaria de los otros hilos. También evita una exposición no deseada de objetos de datos parcialmente construidos. Debido a problemas de reordenación, se puede asignar someData referencia de datos antes de que se completen todas las operaciones de creación de objetos necesarias, incluidas las operaciones del constructor y las asignaciones de valores predeterminadas.

Actualización: De acuerdo con una investigación exhaustiva realizada por @raphw Spring almacena los frijoles singleton en un mapa con vigilancia del monitor. Esto es realmente cierto, como podemos ver en el código fuente de org.springframework.beans.factory.support.DefaultSingletonBeanRegistry :

public Object getSingleton(String beanName, ObjectFactory singletonFactory) { Assert.notNull(beanName, "''beanName'' must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); ... return (singletonObject != NULL_OBJECT ? singletonObject : null); } }

Esto puede proporcionarle propiedades de seguridad de @PostConstruct en @PostConstruct , pero no lo consideraría una garantía suficiente por varios motivos:

  1. Afecta solo a los frijoles de alcance único, sin ofrecer garantías para los frijoles de otros ámbitos: solicitud, sesión, sesión global, alcance de prototipo expuesto accidentalmente, ámbitos de usuario personalizados (sí, puede crear uno por sí mismo).

  2. Asegura que la escritura en someData esté protegida, pero no da garantías al hilo del lector. Aquí se puede construir un ejemplo equivalente, pero simplificado, donde la escritura de datos es monitor-guarder y el subproceso del lector no se ve afectado por ninguna relación que ocurra aquí antes y puede leer datos obsoletos:

    public class Entity { public Object data; public synchronized void setData(Object data) { this.data = data; } }

  3. El último, pero no menos importante: este monitor interno del que estamos hablando es un detalle de la implementación. Al estar indocumentado, no se garantiza que permanezca para siempre y se puede cambiar sin previo aviso.

Nota al margen: Todo lo mencionado anteriormente es válido para los frijoles, que están sujetos a acceso multiproceso. Para los frijoles de ámbito de prototipo no es realmente el caso, a menos que estén expuestos a varios subprocesos explícitamente, por ejemplo, mediante inyección en un frijol de alcance único.

Supongamos que hago alguna inicialización en Spring singleton bean @PostConstruct (código simplificado):

@Service class SomeService { public Data someData; // not final, not volatile public SomeService() { } @PostConstruct public void init() { someData = new Data(....); } }

¿Debería preocuparme por la visibilidad de algunos datos a otros beans y marcarlos como volatile ?

(Supongamos que no puedo inicializarlo en el constructor)

Y segundo escenario: ¿qué @PostConstruct si sobrescribo el valor en @PostConstruct (después de, por ejemplo, la inicialización explícita o la inicialización en el constructor), por lo que escribir en @PostConstruct no se escribirá primero en este atributo?


El framework Spring no está vinculado al lenguaje de programación Java, es solo un framework. Por lo tanto, en general, necesita marcar un campo no final al que se accede por diferentes hilos para ser volatile . Al final del día, un bean Spring no es más que un objeto Java y se aplican todas las reglas de idioma.

final campos final reciben un tratamiento especial en el lenguaje de programación Java. Alexander Shipilev, el actor de rendimiento de Oracle , escribió un excelente artículo sobre este tema. En resumen, cuando un constructor inicializa un campo final , el conjunto para establecer el valor del campo agrega una barrera de memoria adicional que asegura que cualquier hebra vea el campo correctamente.

Para un campo no final , no se crea tal barrera de memoria. Por lo tanto, en general, es perfectamente posible que el método @PostConstruct @PostConstruct inicialice el campo y este valor no sea visto por otro hilo, o peor aún, cuando el constructor solo se ejecuta parcialmente.

¿Esto significa que siempre debe marcar los campos no final como volátiles?

En resumen, sí. Si un campo puede ser accedido por diferentes hilos, lo hace. No cometa el mismo error que cometí cuando solo pensé en el asunto durante unos segundos (gracias a Jk1 por la corrección) y piense en términos de la secuencia de ejecución de su código Java. Podría pensar que el contexto de su aplicación Spring está bloqueado en un solo hilo. Esto significa que el hilo de arranque no tendrá problemas con el campo no volátil. Por lo tanto, podría pensar que todo está en orden siempre y cuando no exponga el contexto de la aplicación a otro hilo hasta que esté completamente inicializado, es decir, se llame al método anotado. Pensando de esta manera, podría suponer que los otros subprocesos no tienen la posibilidad de almacenar en la memoria caché el valor de campo incorrecto siempre que no altere el campo después de este arranque.

En contraste, al código compilado se le permite reordenar las instrucciones, es decir, incluso si se @PostConstruct al método @PostConstruct -annotated antes de que el bean relacionado se exponga a otro hilo en su código Java, esta relación de suceso antes no se retiene necesariamente en el compilado código en tiempo de ejecución. Por lo tanto, otro subproceso siempre puede leer y almacenar en caché el campo no volatile mientras aún no se haya inicializado en absoluto o incluso se haya iniciado parcialmente. Esto puede introducir errores sutiles y, lamentablemente, la documentación de Spring no menciona esta advertencia. Dichos detalles de JMM son una razón por la que personalmente prefiero los campos final y la inyección del constructor.

Actualización : De acuerdo con esta respuesta en otra pregunta , hay escenarios en los que no marcar el campo como volatile aún produciría resultados válidos. Investigué esto un poco más y el marco Spring garantiza, de hecho, una cierta cantidad de sucesos, antes de que la seguridad salga de la caja. Eche un vistazo a la JLS sobre relaciones de suceso antes de que indique claramente:

Se produce un desbloqueo en un monitor: antes de cada bloqueo posterior en ese monitor.

El framework Spring hace uso de esto. Todos los beans se almacenan en un solo mapa y Spring adquiere un monitor específico cada vez que un bean se registra o se recupera de este mapa. Como resultado, el mismo monitor se desbloquea después de registrar el bean completamente inicializado y se bloquea antes de recuperar el mismo bean de otro thread. Esto obliga a este otro hilo a respetar la relación de suceso antes que se refleja en el orden de ejecución de su código Java. Por lo tanto, si reinicia su bean una vez, todos los subprocesos que acceden al bean completamente inicializado verán este estado siempre que accedan al bean de una manera canónica (es decir, la recuperación explícita consultando el contexto de la aplicación o el auto-cableado). Esto hace que, por ejemplo, la inyección de setter o el uso de un método @PostConstruct seguro incluso sin declarar un campo volatile . De hecho, debe evitar volatile campos volatile , ya que introducen una sobrecarga de tiempo de ejecución para cada lectura, lo que puede ser doloroso al acceder a un campo en bucles y porque la palabra clave señala una intención errónea. (Por cierto, por lo que sé, el marco Akka aplica una estrategia similar en la que Akka, aparte de Spring, deja caer algunas líneas sobre el problema).

Sin embargo, esta garantía solo se otorga para la recuperación del bean después de su arranque. Si cambia el campo no volatile después de su arranque o si pierde la referencia del bean durante su inicialización, esta garantía ya no se aplica.

Echa un vistazo a esta entrada de blog anterior que describe esta característica con más detalle. Aparentemente, esta característica no está documentada, ya que incluso la gente de Spring está al tanto (pero no hizo nada al respecto).


La clase SomeService como se define en la pregunta no es segura para subprocesos. Para que la clase sea segura para el hilo, necesitarías:

  1. Hacer el campo someData volatile .
  2. Asegúrese de que la clase de Data sea ​​segura para subprocesos.
  3. Asegúrese de que las instancias de SomeService estén construidas de forma segura: https://www.ibm.com/developerworks/library/j-jtp0618/index.html

Si el campo someData no es volatile , pero se cumplen las condiciones 2 y 3, entonces el código (a diferencia de la clase ), aún podría ser seguro para subprocesos, pero solo si se cumplen otras condiciones que están fuera del control de la clase . Lo más seguro es hacer que la clase sea ​​segura para los hilos. Luego, puede usar la clase en cualquier situación y se garantizará que siempre será seguro para subprocesos.