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
CancelledLe 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?
ExecutorServiceavec une taille de pool de threads de 1?
                BlockingQueue?