java synchronization locking double-checked-locking

java - ¿Este bloqueo doble está roto?



synchronization locking (4)

Checkstyle informa este código como "El idioma de bloqueo comprobado doble está roto", pero no creo que mi código realmente se vea afectado por los problemas con el bloqueo comprobado.

Se supone que el código crea una fila en una base de datos si una fila con esa identificación no existe. Se ejecuta en un entorno de subprocesos múltiples y quiero evitar las excepciones SQL de clave primaria.

El pseudo-código:

private void createRow(int id) { Row row = dao().fetch(id); if (row == null) { synchronized (TestClass.class) { row = dao().fetch(id); if (row == null) { dao().create(id); } } } }

Puedo estar de acuerdo en que parece que el bloqueo se ha verificado dos veces, pero no estoy usando variables estáticas y el código en fetch () y create () es probablemente demasiado complejo para ser inlined y poner fuera de servicio.

¿Estoy equivocado o checkstyle? :)


Creo que en este caso, checkstyle es correcto. En su código tal como se presenta, considere qué sucedería si dos subprocesos tienen una row == null en la entrada al bloque sincronizado. El hilo A entraría en el bloque e insertaría la nueva fila. Luego, después de que el hilo A sale del bloque, el hilo B ingresará en el bloque (porque no sabe lo que acaba de suceder) e intenta insertar la misma nueva fila nuevamente.

Veo que acaba de cambiar el código y agregó una línea que falta bastante importante allí. En el nuevo código, es posible que pueda salirse con la suya, ya que dos hilos no dependerán de los cambios en una variable compartida (estática). Pero es mejor que vea si su DBMS admite una declaración como INSERT OR UPDATE .

Otra buena razón para delegar esta funcionalidad al DBMS es si alguna vez necesita implementar más de un servidor de aplicaciones. Como synchronized bloques synchronized no funcionan en todas las máquinas, de todos modos tendrá que hacer algo más en ese caso.


Si está tentado de escribir código como este, considere:

  • Desde Java 1.4, los métodos de sincronización se han vuelto bastante baratos. No es gratis, pero el tiempo de ejecución realmente no sufre tanto que valga la pena arriesgarse a la corrupción de datos.

  • Desde Java 1.5, tiene las clases Atomic * que le permiten leer y establecer campos de forma atómica. Lamentablemente, no resuelven su problema. Por qué no agregaron AtomicCachedReference o algo así (que llamaría a un método invalidable cuando se llama a get () y el valor actual == null) me supera.

  • Prueba ehcache . Le permite configurar un caché (es decir, un objeto que le permite llamar al código si una clave no está contenida en un mapa). Esto es generalmente lo que quiere y los cachés realmente resuelven su problema (y todos esos otros problemas que usted no sabía que existían).


Suponiendo que quieres que la línea más interna se lea:

row = dao().create(id);

No es un problema de bloqueo comprobado clásico suponiendo que dao().fetch Fetch está muteado correctamente desde el método create.

Editar : (el código fue actualizado)

El problema clásico de un bloqueo comprobado doble es tener un valor asignado antes de que se produzca la inicialización cuando dos subprocesos tienen acceso al mismo valor.

Suponiendo que el DAO está sincronizado correctamente y no devolverá un valor parcialmente inicializado, esto no adolece de los defectos del idioma de bloqueo comprobado.


Como han señalado otros, este código hará lo que usted pretende tal como es, pero solo bajo un estricto conjunto de suposiciones no obvias:

  1. El código de Java no está agrupado (ver la respuesta de @Greg H)
  2. La referencia de "fila" solo se verifica con nulo en la primera línea, antes del bloque de sincronización.

La razón por la que el idioma de bloqueo comprobado doble está roto (según la sección 16.2.4 de Java Concurrency in Practice ) es que es posible que un hilo que ejecuta este método vea una referencia no nula pero inicializada incorrectamente a "row", antes de ingresar al bloque sincronizado (a menos que "dao" proporcione la sincronización adecuada). Si su método estaba haciendo algo con "fila", aparte de comprobar que es nulo o no, se rompería. Tal como están las cosas, probablemente sea correcto pero muy frágil : personalmente no me sentiría cómodo cometiendo este código si pensara que había alguna posibilidad remota de que algún otro desarrollador en algún momento posterior pudiera modificar el método sin entender las sutilezas de DCL.