我有方法TaskManager.newRepeatingTask(Runnable r, long delay, long interval)
并返回UUID
。我为该方法返回的内容分配了一个UUID
变量,我想在Runnable
内部使用该变量。我将如何有效地完成这项工作,或者我将在这里完成的工作?
UUID id = TaskManager.newRepeatingTask(() -> {
for (int i = 0; i < profileButtons.length; i++) {
GuiButton button = profileButtons[i];
button.move(-1, 0);
}
toMove--;
if (toMove == 0) {
// id: "The variable may not have been initialized"
TaskManager.cancelTask(id);
}
}, 30L, 0L);
答案 0 :(得分:5)
当您不确定哪个将首先到达时,使用java.util.concurrent.CompletableFuture
(Java 8中的新增功能)在线程或任务之间传输值。方法如下:
CompletableFuture<UUID> id = new CompletableFuture<>();
id.complete(TaskManager.newRepeatingTask(() -> {
for (int i = 0; i < profileButtons.length; i++) {
GuiButton button = profileButtons[i];
button.move(-1, 0);
}
toMove--;
if (toMove == 0) {
TaskManager.cancelTask(id.join());
}
}, 30L, 0L));
join()
方法将收集并返回complete()
方法提供的值。如果尚未调用complete()
,join()
将会阻止,直到CompletableFuture
为止。 TaskManager.newRepeatingTask()
在内部处理所有同步和内存可见性问题。
正如其他人所说,这有点人为。重复任务取消自身的更常规方法是让它返回一个布尔值,指示是否应该重新安排或取消它。要执行此操作,请更改Supplier<Boolean>
以取Runnable
而不是{{1}}。
答案 1 :(得分:4)
您可以让TaskManager.newRepeatingTask
接受Consumer&lt; UUID&gt;。然后使用当时已知的UUID
创建一个runnable。
所以你在内部做这样的事情:
//inside newRepeatingTask(consumer:Consumer<UUID> ...)
Runnable r = new Runnable() {
public UUID uuid;
@Override
public void run() {
consumer.accept(uuid); //calls the lambda
}
};
r.uuid = getNextUUID(); //do whatever here
//add to your presumed list of runnables
现在你可以这样做:
UUID id = TaskManager.newRepeatingTask((UUID id) -> {
TaskManager.cancelTask(id);
//probably do something better with id
}, 30L, 0L);
//LOOK MA, this code is DRY
答案 2 :(得分:3)
我认为你在这里必须要复杂一点。我想到的第一件事是:
Runnable
uuid
(匿名)子类
newRepeatingTask
并获取UUID 那将是:
Runnable r = new Runnable() {
public UUID uuid;
@Override
public void run() {
for (int i = 0; i < profileButtons.length; i++) {
GuiButton button = profileButtons[i];
button.move(-1, 0);
}
toMove--;
if (toMove == 0) {
// id: "The variable may not have been initialized"
TaskManager.cancelTask(uuid);
}
}
}
UUID id = TaskManager.newRepeatingTask(r, 30L, 0L);
r.uuid = id;
抱歉,但我认为你不得不放弃lambda:&#39;(
重要提示:正如@Dici所述,如果runnable在newRepeatingTask
内运行,则可能会出现一些同步问题。你可以考虑AlexanderBrevig建议的选项,它允许你在runnable上调用run()
之前设置id。
答案 3 :(得分:2)
我的第一个解决方案,如果我可以控制TaskManager
,则会更改它,以便它还将UUID参数传递给回调或使用另一种控制方法 - 然后使用该方法的结果没有实际意义。
然而,如果我没有那么..
(编辑:我已被告知在Java 8中处理此问题的正确方法是使用CompletableFuture - 请参阅Stuarts的答案。)
另一种方法是使用“可变参考包装器”,如Holder<T>
(或T[]
或自己创建)来模拟可变绑定。然后,
Holder<UUID> idRef = new Holder<UUID>(); // Effectively final
idRef.value = TaskManager.newRepeatingTask(() -> {
for (int i = 0; i < profileButtons.length; i++) {
GuiButton button = profileButtons[i];
button.move(-1, 0);
}
toMove--;
if (toMove == 0) {
UUID id = idRef.value;
TaskManager.cancelTask(id);
}
}, 30L, 0L);
与Runnable-with-UUID方法类似,此也在ID的分配和lambda / Runnable 中的潜在用法之间存在潜在的竞争条件,如果该任务在不同的线程上运行。 (如果稍后在同一个线程上运行,则不需要应用同步问题;如果在同一个线程上立即运行,则在lambda中永远不会观察到UUID。)
除非立即调用Runnable,否则应用外部/包装方法调用本身(以及适用代码周围)的共享同步应该处理。出于保证可见性原因(即使没有“竞争条件”),如果采用这种方法并且任务可以在不同的线程上执行,则应该无论如何进行同步或等效。
Holder<UUID> idRef = new Holder<UUID>();
synchronized(idRef) {
idRef.value = TaskManager.newRepeatingTask(() -> {
for (int i = 0; i < profileButtons.length; i++) {
GuiButton button = profileButtons[i];
button.move(-1, 0);
}
toMove--;
if (toMove == 0) {
// id: "The variable may not have been initialized"
synchronized(idRef) {
UUID id = idRef.value;
TaskManager.cancelTask(id);
}
}
}, 30L, 0L);
}