practice español java multithreading lazy-initialization lock-free wait-free

java - español - Inicialización perezosa sin trabas y sin espera de subprocesos



java concurrency in practice epub (5)

¿Qué hay de usar otra variable volatile para bloquear? ¿Puedes hacer el doble bloqueo con nueva variable?

Para realizar una inicialización perezosa sin bloqueo y sin espera, hago lo siguiente:

private AtomicReference<Foo> instance = new AtomicReference<>(null); public Foo getInstance() { Foo foo = instance.get(); if (foo == null) { foo = new Foo(); // create and initialize actual instance if (instance.compareAndSet(null, foo)) // CAS succeeded return foo; else // CAS failed: other thread set an object return instance.get(); } else { return foo; } }

y funciona bastante bien, excepto por una cosa: si dos subprocesos ven la instancia null , ambos crean un nuevo objeto, y solo uno tiene la suerte de configurarlo mediante la operación de CAS, lo que lleva al desperdicio de recursos.

¿Alguien sugiere otro patrón de inicialización perezoso sin bloqueo , que disminuya la probabilidad de crear dos objetos caros por dos hilos concurrentes?


Creo que necesitas tener algo de sincronización para la creación del objeto en sí. Yo lo haría:

// The atomic reference itself must be final! private final AtomicReference<Foo> instance = new AtomicReference<>(null); public Foo getInstance() { Foo foo = instance.get(); if (foo == null) { synchronized(instance) { // You need to double check here // in case another thread initialized foo Foo foo = instance.get(); if (foo == null) { foo = new Foo(); // actual initialization instance.set(foo); } } } return foo; }

Este es un patrón muy común especialmente para los singletons perezosos. El bloqueo de doble comprobación minimiza el número de veces que se ejecuta realmente el bloque synchronized .


No estoy seguro de si el resultado final debe estar centrado en el rendimiento o no, si la respuesta a continuación no es la solución por favor, puede verificar dos veces, por ejemplo, y después de la primera verificación, el método "thread.sleep" muestra milisegundos aleatorios menores a 100 mili segundos.

private AtomicBoolean canWrite = new AtomicBoolean(false); private volatile Foo foo; public Foo getInstance() { if(foo==null){ Thread.Sleep(getRandomLong(50)) // you need to write method for it if(foo==null){ foo = new Foo(); } } return foo; }


Probablemente me gustaría ir con el patrón Singleton de inicio perezoso:

private Foo() {/* Do your heavy stuff */} private static class CONTAINER { private static final Foo INSTANCE = new Foo(); } public static Foo getInstance() { return CONTAINER.INSTANCE; }

Realmente no veo ninguna razón sobre el uso de un campo miembro de AtomicReference para sí mismo.


Si quieres una verdadera libertad de bloqueo, tendrás que girar un poco. Puedes tener derechos de creación de ''ganar'' de un hilo, pero los demás deben girar hasta que esté listo.

private AtomicBoolean canWrite = new AtomicBoolean(false); private volatile Foo foo; public Foo getInstance() { while (foo == null) { if(canWrite.compareAndSet(false, true)){ foo = new Foo(); } } return foo; }

Obviamente, esto tiene sus problemas de rotación ocupada (puede poner un sueño o un rendimiento allí), pero probablemente recomendaría la en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom .