Question
Comment créer un chargeur d'arrière-plan approprié dans Java 8? Les conditions:
- les données doivent être chargées en arrière-plan
- après le chargement, les données doivent être affichées
- pendant le chargement des données, aucune autre demande ne doit être acceptée
- s'il y avait des demandes pendant le chargement des données, un autre chargement devrait être planifié après un certain délai (par exemple 5 secondes)
Le but est par exemple de faire accepter les demandes de rechargement, mais pas la base de données inondée de demandes.
MCVE
Voici un MCVE. Il s'agit d'une tâche en arrière-plan qui simule le chargement en appelant simplement Thread.sleep pendant 2 secondes. La tâche est planifiée toutes les secondes, ce qui entraîne naturellement un chevauchement des tâches de chargement en arrière-plan, ce qui doit être évité.
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);
}
}
La sortie est la suivante:
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
Le but est d'avoir par exemple # 1 et # 2 sautés parce que # 0 est toujours en cours d'exécution.
Problème
Où définissez-vous correctement le mécanisme de blocage? La synchronisation doit-elle être utilisée? Ou certains AtomicBoolean
? Et si oui, cela devrait-il être à l'intérieur de la get()
méthode ou ailleurs?
la source
ExecutorService
avec une taille de pool de threads de 1?BlockingQueue
?Réponses:
Vous disposez déjà d'un pool de threads pour exécuter la tâche. Ce n'est pas nécessairement compliqué d'exécuter la tâche dans un autre exécuteur asynchrone (
ForkJoinPool
lorsque vous utilisezCompletableFuture
)Faites simple:
ScheduledExecutorService s'assurera qu'une seule tâche est exécutée à la fois lorsque vous l'avez invoquée avec scheduleAtFixedRate
la source
Prenant ce qui suit comme exigences:
La solution peut être construite sur la base de
Executors.newSingleThreadExecutor()
,CompletableFuture
etLinkedBlockingQueue
:Après l'exécution, la sortie standard aura la sortie suivante:
la source
J'ai ajouté un AtomicInteger qui servira de compteur pour l'exécution de tâches avec les méthodes simples lock () et unlock () avec cette modification mineure dans votre code d'origine, j'ai obtenu la sortie:
Voici ma solution pour votre tâche:
MISE À JOUR
J'ai changé les méthodes lock () et unlock () en une forme plus simple:
la source
Si vous comprenez, vous avez plusieurs tâches en arrière-plan en même temps. comme ces tâches font exactement le même travail, vous ne voulez pas les exécuter en parallèle, vous avez besoin d'une tâche pour terminer le travail et partager ses résultats avec d'autres. Donc, si vous obtenez 10
CompletableFuture
simultanément, vous voulez que l'un d'eux appelle «recharger» dans db et partage les résultats d'exécution avec les autres de manière à ce que toutCompletableFuture
se termine normalement avec le résultat. Je suppose queet
si mes suppositions sont correctes, vous pouvez essayer ma solution.
J'ai une sorte de relation parent-enfant entre les tâches. la tâche parentale est celle qui fait vraiment son travail et partage le résultat obtenu avec ses enfants. la tâche enfant est une tâche qui a été ajoutée alors que la tâche parent était encore en cours d'exécution, la tâche enfant attend que la tâche parent termine son exécution. Comme les résultats de la tâche des parents sont encore «frais», ils sont copiés dans chaque enfant et tous terminent leur avenir.
Et voici la sortie:
la source
si vous voulez juste un seul thread d'accès, une simple synchronisation ferait l'affaire ...
production:
code:
la source
J'ai essayé une solution en utilisant un commutateur double Thread, voir la classe
BackgroundTaskDualSwitch
, il simule le chargement en utilisant leCompletableFuture
. L'idée est de laisser une deuxième tâche attendre que la tâche en cours d'exécution soit terminée, voir modificationBackgroundTask
. Cela garantit que max une tâche Thread est en cours d'exécution et max une tâche Thread est en attente. Les autres demandes sont ignorées jusqu'à ce que la tâche en cours soit terminée et devienne «libre» pour gérer la demande suivante.La sortie est:
la source
Le premier thread qui commence à faire le travail coûteux notifiera avec un rappel le résultat. Les autres threads qui tentent de l'exécuter seront enregistrés dans ExpensiveWork.notificables, donc une fois le travail coûteux terminé, le thread qui a effectué le travail les avertira.
Pendant ce temps, les discussions vérifient le résultat toutes les 5 secondes.
Et voici la sortie:
la source