java - 如何在 Java 8 中创建阻塞后台加载程序?

标签 java multithreading java-8

问题

如何在 Java 8 中创建合适的后台加载程序?条件:

  • 数据应在后台加载
  • 加载后应显示数据
  • 加载数据后,不应接受进一步的请求
  • 如果在加载数据时有请求,则应在一定的超时(例如 5 秒)后安排另一次加载

目的是 e. G。已接受重新加载请求,但数据库未充满请求。

MCVE

这是一个 MCVE。它由一个后台任务组成,该任务通过简单地调用 Thread.sleep 2 秒来模拟加载。每秒调度一次任务,这自然会导致后台加载任务重叠,这是应该避免的。

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);
  }

}

输出是这样的:

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

目标是拥有 e。 G。 #1 和 #2 已跳过,因为 #0 仍在运行。

问题

你在哪里正确设置了阻止机制?是否应该使用同步?或者一些AtomicBoolean?如果是这样,它应该在 get() 方法内部还是其他地方?

最佳答案

您已经有一个线程池来执行任务。 在另一个异步执行器中运行任务并不一定会让事情变得复杂(当您使用CompletableFuture时,ForkJoinPool)

让事情变得简单:

public static void loadInBackground(int taskId) {
    // create the loading task
    BackgroundTask backgroundTask = new BackgroundTask(taskId);
    // No need to run in async, as it already in executor
    backgroundTask.run();
}

当您使用scheduleAtFixedRate调用它时,ScheduledExecutorService将确保一次只运行一个任务。

Creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently with the given period; that is executions will commence after initialDelay then initialDelay+period, then initialDelay + 2 * period, and so on. If any execution of the task encounters an exception, subsequent executions are suppressed. Otherwise, the task will only terminate via cancellation or termination of the executor. If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute.

关于java - 如何在 Java 8 中创建阻塞后台加载程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58796231/

相关文章:

c# - 无法在单元测试中复制 'cross-thread operation not valid' 错误 - 我不明白

c - 同一台机器相同的程序不同的cpu时间。为什么?

java - 如何将 java 8 的 lambda 转换为 java 7?

java - 调用 @Singleton bean 的方法

java - 为什么是Arraylist<>。 sublist() 未映射到用户定义的数据类型 arraylist<>?

java - 为什么堆内存在 Java 8 中与元空间一起上升?

java - 如何在Java中修剪对象的某些字段?

java - 当适配器处于同一 Activity 中时,在回收器 View 中添加搜索 View

java - Java的for循环中是否有线程安全优化?

java - Java编译器是否优化流过滤?