将任务移动到单独的线程,避免重复任务 运行 并正确使用 Executors + Future
Moving a task to a separate thread, avoid duplication while task running & proper use of Executors + Future
我们在遗留代码和当前代码之间有一个粘合组件。本质上,整个遗留应用程序都是单线程的,并且存在可怕的问题,其中 ui 刷新单个指令可能会发生 5 到 8 次。
我想在第一个更新请求发生 +2 秒后发布异步消息。
我们不要纠结于为什么,这不是我真正想做的,但我必须至少了解如何做到这一点,这样我才能实施真正的解决方案。
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(2);
messageBus.publishAsynch(new LegacyUiUpdateEvent());
} catch (InterruptedException e) {
// TODO Log something
Thread.currentThread().interrupt();
}
};
@Override
public void update(Observable arg0, Object arg1) {
ExecutorService executor = Executors.newSingleThreadExecutor();
if (futureTask == null || futureTask.isDone()) {
futureTask = executor.submit(task);
try {
executor.awaitTermination(10, TimeUnit.SECONDS);
executor.shutdownNow();
} catch (InterruptedException e) {
// TODO Log something
Thread.currentThread().interrupt();
}
}
}
理论是:如果未来的任务不存在,我们创建它,一旦它在那里,如果它没有完成(因为这是错误的遗留更新 4/x,其中x ∈ [5,12] 并且睡眠仍然有效)那么我们完全跳过并且不创建新的执行者。
问题是 ,据我所知,executor.submit(task)
实际上并没有发生在新的轨道上。就像我说的遗留应用程序是单线程的,在我将睡眠时间增加到 15 秒后,很明显它正在将整个当前线程发送到睡眠状态。
我如何将我的任务放在一个全新的线程上(使用 concurrency
库)并避免多次执行该任务,即使更新方法被调用了太多次(并且100% 不受我控制)。我认为 future.isDone()
有效,但不是 100%
executor.submit()
确实在新线程中启动任务,但紧接着 executor.awaitTermination(10, TimeUnit.SECONDS);
在 当前 线程中等待任务完成。不需要在 current 线程中等待,但是确实需要有一种方法来确定任务是否已经 运行.
麻烦的部分是每次都创建 ExecutorService
- 没有必要每次都重新创建它。它可以作为 class 的实例变量并重新使用。理想情况下,它将通过构造函数注入,因此创建它的 class 可以将其关闭 if that's really needed.
private final ExecutorService executor = Executors.newSingleThreadExecutor(); // or injected through constructor
private Future<?> futureTask;
@Override
public void update(Observable arg0, Object arg1) {
if (futureTask == null || futureTask.isDone()) {
futureTask = executor.submit(task);
}
}
如果您的 Java 8 或更高,这是更好的做法
CompletableFuture.runAsync(task);
因为这将在由 JVM 管理的 Fork-join 线程池上执行,您不必担心创建它或关闭它。当然,这将 运行 异步地满足您的要求。
我们在遗留代码和当前代码之间有一个粘合组件。本质上,整个遗留应用程序都是单线程的,并且存在可怕的问题,其中 ui 刷新单个指令可能会发生 5 到 8 次。
我想在第一个更新请求发生 +2 秒后发布异步消息。
我们不要纠结于为什么,这不是我真正想做的,但我必须至少了解如何做到这一点,这样我才能实施真正的解决方案。
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(2);
messageBus.publishAsynch(new LegacyUiUpdateEvent());
} catch (InterruptedException e) {
// TODO Log something
Thread.currentThread().interrupt();
}
};
@Override
public void update(Observable arg0, Object arg1) {
ExecutorService executor = Executors.newSingleThreadExecutor();
if (futureTask == null || futureTask.isDone()) {
futureTask = executor.submit(task);
try {
executor.awaitTermination(10, TimeUnit.SECONDS);
executor.shutdownNow();
} catch (InterruptedException e) {
// TODO Log something
Thread.currentThread().interrupt();
}
}
}
理论是:如果未来的任务不存在,我们创建它,一旦它在那里,如果它没有完成(因为这是错误的遗留更新 4/x,其中x ∈ [5,12] 并且睡眠仍然有效)那么我们完全跳过并且不创建新的执行者。
问题是 ,据我所知,executor.submit(task)
实际上并没有发生在新的轨道上。就像我说的遗留应用程序是单线程的,在我将睡眠时间增加到 15 秒后,很明显它正在将整个当前线程发送到睡眠状态。
我如何将我的任务放在一个全新的线程上(使用 concurrency
库)并避免多次执行该任务,即使更新方法被调用了太多次(并且100% 不受我控制)。我认为 future.isDone()
有效,但不是 100%
executor.submit()
确实在新线程中启动任务,但紧接着 executor.awaitTermination(10, TimeUnit.SECONDS);
在 当前 线程中等待任务完成。不需要在 current 线程中等待,但是确实需要有一种方法来确定任务是否已经 运行.
麻烦的部分是每次都创建 ExecutorService
- 没有必要每次都重新创建它。它可以作为 class 的实例变量并重新使用。理想情况下,它将通过构造函数注入,因此创建它的 class 可以将其关闭 if that's really needed.
private final ExecutorService executor = Executors.newSingleThreadExecutor(); // or injected through constructor
private Future<?> futureTask;
@Override
public void update(Observable arg0, Object arg1) {
if (futureTask == null || futureTask.isDone()) {
futureTask = executor.submit(task);
}
}
如果您的 Java 8 或更高,这是更好的做法
CompletableFuture.runAsync(task);
因为这将在由 JVM 管理的 Fork-join 线程池上执行,您不必担心创建它或关闭它。当然,这将 运行 异步地满足您的要求。