Pregunta
¿Cómo se crea un cargador de fondo adecuado en Java 8? Las condiciones:
- los datos deben cargarse en segundo plano
- después de cargar los datos deben mostrarse
- mientras se cargan los datos, no se deben aceptar más solicitudes
- si hubo solicitudes mientras se cargaban los datos, se debe programar otra carga después de un cierto tiempo de espera (por ejemplo, 5 segundos)
El propósito es, por ejemplo, tener solicitudes de recarga aceptadas, pero no la base de datos inundada con las solicitudes.
MCVE
Aquí hay un MCVE. Consiste en una tarea en segundo plano que simula la carga simplemente invocando Thread.sleep durante 2 segundos. La tarea se programa cada segundo, lo que naturalmente conlleva una superposición de las tareas de carga en segundo plano, lo que debe evitarse.
public class LoadInBackgroundExample {
/**
* A simple background task which should perform the data loading operation. In this minimal example it simply invokes Thread.sleep
*/
public static class BackgroundTask implements Runnable {
private int id;
public BackgroundTask(int id) {
this.id = id;
}
/**
* Sleep for a given amount of time to simulate loading.
*/
@Override
public void run() {
try {
System.out.println("Start #" + id + ": " + Thread.currentThread());
long sleepTime = 2000;
Thread.sleep( sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("Finish #" + id + ": " + Thread.currentThread());
}
}
}
/**
* CompletableFuture which simulates loading and showing data.
* @param taskId Identifier of the current task
*/
public static void loadInBackground( int taskId) {
// create the loading task
BackgroundTask backgroundTask = new BackgroundTask( taskId);
// "load" the data asynchronously
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
CompletableFuture<Void> future = CompletableFuture.runAsync(backgroundTask);
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return "task " + backgroundTask.id;
}
});
// display the data after they are loaded
CompletableFuture<Void> future = completableFuture.thenAccept(x -> {
System.out.println( "Background task finished:" + x);
});
}
public static void main(String[] args) {
// runnable which invokes the background loader every second
Runnable trigger = new Runnable() {
int taskId = 0;
public void run() {
loadInBackground( taskId++);
}
};
// create scheduler
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> beeperHandle = scheduler.scheduleAtFixedRate(trigger, 0, 1, TimeUnit.SECONDS);
// cancel the scheudler and the application after 10 seconds
scheduler.schedule(() -> beeperHandle.cancel(true), 10, TimeUnit.SECONDS);
try {
beeperHandle.get();
} catch (Throwable th) {
}
System.out.println( "Cancelled");
System.exit(0);
}
}
El resultado es este:
Start #0: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Start #1: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Start #2: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Finish #0: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Background task finished:task 0
Finish #1: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Background task finished:task 1
Start #3: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Finish #2: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Background task finished:task 2
Start #4: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Start #5: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Finish #3: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Background task finished:task 3
Start #6: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Finish #4: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Background task finished:task 4
Finish #5: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Background task finished:task 5
Start #7: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Finish #6: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Start #8: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Background task finished:task 6
Start #9: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Finish #7: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Background task finished:task 7
Start #10: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Finish #8: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Background task finished:task 8
Cancelled
El objetivo es, por ejemplo, omitir # 1 y # 2 porque el # 0 todavía se está ejecutando.
Problema
¿Dónde configuras correctamente el mecanismo de bloqueo? ¿Debería usarse la sincronización? O algo AtomicBoolean
? Y si es así, ¿debería estar dentro del get()
método o en otro lugar?
fuente
ExecutorService
tamaño de grupo de subprocesos de 1?BlockingQueue
?Respuestas:
Ya tiene un conjunto de subprocesos para ejecutar la tarea. No es necesariamente y complica las cosas ejecutar la tarea en otro ejecutor asíncrono (
ForkJoinPool
cuando lo usasCompletableFuture
)Hazlo simple:
ScheduledExecutorService se asegurará de que solo se ejecute una tarea a la vez cuando la invocó con scheduleAtFixedRate
fuente
Tomando los siguientes requisitos:
La solución se puede construir en base a
Executors.newSingleThreadExecutor()
,CompletableFuture
yLinkedBlockingQueue
:Después de la ejecución, el stdout tendrá el siguiente resultado:
fuente
He agregado un AtomicInteger que actuará como un contador para ejecutar tareas con métodos simples de bloqueo () y desbloqueo () con este pequeño cambio en su código original. Obtuve la salida:
Aquí está mi solución para su tarea:
ACTUALIZAR
He cambiado los métodos lock () y unlock () a una forma más simple:
fuente
Si entiende, tiene varias tareas en segundo plano al mismo tiempo. Como estas tareas están haciendo exactamente el mismo trabajo, no desea ejecutarlas en paralelo, necesita una tarea para terminar el trabajo y compartir sus resultados con los demás. Entonces, si obtiene 10
CompletableFuture
simultáneamente, desea que uno de ellos llame 'recargar' en db y comparta los resultados de ejecución con otros de una manera que todoCompletableFuture
se complete normalmente con el resultado. Asumo esto dey
Si mis suposiciones son correctas, puedes probar mi solución.
Tengo algún tipo de relación padre-hijo entre tareas. La tarea principal es la que realmente está haciendo su trabajo y comparte el resultado obtenido con sus hijos. La tarea secundaria es una tarea que se agregó mientras la tarea principal aún se estaba ejecutando, la tarea secundaria espera hasta que la tarea principal finalice su ejecución. Como los resultados de la tarea de los padres todavía son "frescos", se copian en cada niño y todos ellos completan su futuro.
Y aquí está el resultado:
fuente
si solo desea un único hilo de acceso, un simple sincronizado haría el trabajo ...
salida:
código:
fuente
Intenté una solución usando un interruptor dual Thread, vea la clase
BackgroundTaskDualSwitch
, simula la carga usandoCompletableFuture
. La idea es dejar que una segunda tarea espere hasta que finalice la tarea actualmente en ejecución, ver el cambioBackgroundTask
. Esto garantiza que se ejecute un máximo de un subproceso de tarea y un máximo de un subproceso de tarea esté esperando. Se omiten más solicitudes hasta que se completa la tarea en ejecución y se vuelve 'libre' para manejar la siguiente solicitud.Salida es:
fuente
El primer hilo que comienza a hacer el trabajo costoso notificará con una devolución de llamada el resultado. Otros subprocesos que intenten ejecutarlo se registrarán en ExpensiveWork.notificables, por lo que una vez que el trabajo costoso termine, el subproceso que realizó el trabajo los notificará.
Mientras tanto, los hilos verifican el resultado cada 5 segundos.
Y esta es la salida:
fuente