thread safe example java thread-safety lazy-initialization

java - safe - ¿Cómo implementar la inicialización perezosa de subproceso seguro?



java singleton example (11)

¿Cuáles son algunos enfoques recomendados para lograr una inicialización perezosa segura de subprocesos ? Por ejemplo,

// Not thread-safe public Foo getInstance(){ if(INSTANCE == null){ INSTANCE = new Foo(); } return INSTANCE; }


Aquí hay un enfoque más que se basa en un solo ejecutor semántico.

La solución completa con un montón de ejemplos de uso se puede encontrar en github ( https://github.com/ManasjyotiSharma/java_lazy_init ). Aquí está el quid de la misma:

El semántico "One Time Executor" como su nombre indica tiene las siguientes propiedades:

  1. Un objeto envoltorio que envuelve una función F. En el contexto actual, F es una expresión function / lambda que contiene el código de inicialización / desinicialización.
  2. La envoltura proporciona un método de ejecución que se comporta como:

    • Llama a la función F la primera vez que se ejecuta la ejecución y se almacena en caché la salida de F.
    • Si la llamada de 2 o más subprocesos se ejecuta al mismo tiempo, solo uno “entra” y los otros bloquean hasta que se termina el “que ingresó”.
    • Para todas las demás / futuras invocaciones de ejecución, no llama a F sino que simplemente devuelve la salida previamente almacenada en caché.
  3. Se puede acceder de forma segura a la salida en caché desde fuera del contexto de inicialización.

Esto se puede utilizar para la inicialización, así como también para la inicialización no idempotente.

import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; /** * When execute is called, it is guaranteed that the input function will be applied exactly once. * Further it''s also guaranteed that execute will return only when the input function was applied * by the calling thread or some other thread OR if the calling thread is interrupted. */ public class OneTimeExecutor<T, R> { private final Function<T, R> function; private final AtomicBoolean preGuard; private final CountDownLatch postGuard; private final AtomicReference<R> value; public OneTimeExecutor(Function<T, R> function) { Objects.requireNonNull(function, "function cannot be null"); this.function = function; this.preGuard = new AtomicBoolean(false); this.postGuard = new CountDownLatch(1); this.value = new AtomicReference<R>(); } public R execute(T input) throws InterruptedException { if (preGuard.compareAndSet(false, true)) { try { value.set(function.apply(input)); } finally { postGuard.countDown(); } } else if (postGuard.getCount() != 0) { postGuard.await(); } return value(); } public boolean executed() { return (preGuard.get() && postGuard.getCount() == 0); } public R value() { return value.get(); } }

Aquí hay un ejemplo de uso:

import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; /* * For the sake of this example, assume that creating a PrintWriter is a costly operation and we''d want to lazily initialize it. * Further assume that the cleanup/close implementation is non-idempotent. In other words, just like initialization, the * de-initialization should also happen once and only once. */ public class NonSingletonSampleB { private final OneTimeExecutor<File, PrintWriter> initializer = new OneTimeExecutor<>( (File configFile) -> { try { FileOutputStream fos = new FileOutputStream(configFile); OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8); BufferedWriter bw = new BufferedWriter(osw); PrintWriter pw = new PrintWriter(bw); return pw; } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } } ); private final OneTimeExecutor<Void, Void> deinitializer = new OneTimeExecutor<>( (Void v) -> { if (initializer.executed() && null != initializer.value()) { initializer.value().close(); } return null; } ); private final File file; public NonSingletonSampleB(File file) { this.file = file; } public void doSomething() throws Exception { // Create one-and-only-one instance of PrintWriter only when someone calls doSomething(). PrintWriter pw = initializer.execute(file); // Application logic goes here, say write something to the file using the PrintWriter. } public void close() throws Exception { // non-idempotent close, the de-initialization lambda is invoked only once. deinitializer.execute(null); } }

Para algunos ejemplos más (por ejemplo, la inicialización de singleton que requiere algunos datos disponibles solo en tiempo de ejecución, por lo que no se puede crear una instancia en un bloque estático), consulte el enlace de github mencionado anteriormente.


Dependiendo de lo que trates de lograr:

Si desea que todos los subprocesos compartan la misma instancia, puede sincronizar el método. Esto sera suficiente

Si desea realizar una INSTANCIA por separado para cada Thread, debe usar java.lang.ThreadLocal


Esto se puede hacer sin bloqueo utilizando AtomicReference como titular de la instancia:

// in class declaration 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; } }

La principal desventaja aquí es que varios subprocesos pueden instanciar simultáneamente dos o más objetos Foo , y solo uno tendrá la suerte de ser configurado, por lo que si la creación de instancias requiere E / S u otro recurso compartido, este método puede no ser adecuado.

En el otro lado, este enfoque es sin bloqueo y sin espera : si un hilo que ingresó por primera vez a este método está atascado, no afectará la ejecución de otros.


Intente definir el método que obtiene una instancia como sincronizada:

public synchronized Foo getInstance(){ if(INSTANCE == null){ INSTANCE = new Foo(); } return INSTANCE; }

O usa una variable:

private static final String LOCK = "LOCK"; public synchronized Foo getInstance(){ synchronized(LOCK){ if(INSTANCE == null){ INSTANCE = new Foo(); } } return INSTANCE; }


La forma más fácil es usar una clase de soporte interno estático:

public class Singleton { private Singleton() { } public static Singleton getInstance() { return Holder.INSTANCE; } private static class Holder { private static final Singleton INSTANCE = new Singleton(); } }


Para singletons, existe una solución elegante al delegar la tarea al código JVM para la inicialización estática.

public class Something { private Something() { } private static class LazyHolder { public static final Something INSTANCE = new Something(); } public static Something getInstance() { return LazyHolder.INSTANCE; } }

ver

http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom

y esta entrada de blog de Crazy Bob Lee

http://blog.crazybob.org/2007/01/lazy-loading-singletons.html


Pensando en la inicialización perezosa, esperaría que se obtuviera un objeto "casi real" que simplemente decora el objeto aún no inicializado.

Cuando se invoque el primer método, se inicializará la instancia dentro de la interfaz decorada.

* Debido al uso del Proxy, el objeto iniciado debe implementar la interfaz pasada.

* La diferencia con otras soluciones es la encapsulación de la iniciación desde el uso. Empieza a trabajar directamente con DataSource como si se hubiera inicializado. Se inicializará en la invocación del primer método.

Uso:

DataSource ds = LazyLoadDecorator.create(dsSupplier, DataSource.class)

Entre bastidores:

public class LazyLoadDecorator<T> implements InvocationHandler { private final Object syncLock = new Object(); protected volatile T inner; private Supplier<T> supplier; private LazyLoadDecorator(Supplier<T> supplier) { this.supplier = supplier; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (inner == null) { synchronized (syncLock) { if (inner == null) { inner = load(); } } } return method.invoke(inner, args); } protected T load() { return supplier.get(); } @SuppressWarnings("unchecked") public static <T> T create(Supplier<T> factory, Class<T> clazz) { return (T) Proxy.newProxyInstance(LazyLoadDecorator.class.getClassLoader(), new Class[] {clazz}, new LazyLoadDecorator<>(factory)); } }


Ponga el código en un bloque synchronized con algún bloqueo adecuado. Existen otras técnicas altamente especializadas, pero sugeriría evitarlas a menos que sea absolutamente necesario.

También ha usado el caso SHOUTY, que tiende a indicar un método static pero de instancia. Si es realmente estático, le sugiero que se asegure de que no sea mutable de ninguna manera. Si solo es caro crear una estática inmutable, la carga de clases es perezosa de todos modos. Es posible que desee moverlo a una clase diferente (posiblemente anidada) para retrasar la creación hasta el último momento absoluto.


Si está usando Apache Commons Lang , entonces puede usar una de las variaciones de ConcurrentInitializer como LazyInitializer .

Ejemplo:

lazyInitializer = new LazyInitializer<Foo>() { @Override protected Foo initialize() throws ConcurrentException { return new Foo(); } };

Ahora puedes obtener de forma segura a Foo (se inicializa solo una vez):

Foo instance = lazyInitializer.get();

Si estás utilizando la guayaba de Google :

Supplier<Foo> fooSupplier = Suppliers.memoize(new Supplier<Foo>() { public Foo get() { return new Foo(); } });

Entonces llámalo por Foo f = fooSupplier.get();

Desde Suppliers.memoize javadoc :

Devuelve un proveedor que almacena en caché la instancia recuperada durante la primera llamada a get () y devuelve ese valor en las llamadas posteriores a get (). El proveedor devuelto es seguro para subprocesos . El método get () del delegado se invocará como máximo una vez . Si delegar es una instancia creada por una llamada anterior para memorizar, se devuelve directamente.


Si usa lombok en su proyecto, puede usar una característica que se describe here .

Simplemente crea un campo, lo anota con @Getter(lazy=true) y agrega la inicialización, de esta manera: @Getter(lazy=true) private final Foo instance = new Foo();

Tendrá que hacer referencia al campo solo con getter (vea las notas en here lombok), pero en la mayoría de los casos eso es lo que necesitamos.