我有一个带有固定线程池的类,我用它来多次运行一个过程。该过程的一部分涉及创建一个 Runnable
,并将其传递给 SwingUtilities.invokeAndWait
以更新我的 Swing GUI。
当我发出再次运行过程的请求时,我希望能够抢占任何正在运行的过程。为此,我保留了最后一次 submit()
调用中的 Future
,以便我可以 cancel()
它。我还保留了最后一个 Runnable,这样我就可以在它上面设置一个标志,告诉它在 run() 时不执行任何操作,因为它可能已传递到 AWT 事件线程因此我无法通过取消我的 future
来阻止它。
但是,无论我做什么,Runnable
都会被执行。这是一个精简的示例:
class Procedure {
private AtomicReference<Updater> updater;
private AtomicReference<Future<?>> currProcedure;
private ExecutorService threadPool;
private Runnable theProcedure;
public Procedure() {
updater = new AtomicReference<Updater>();
currProcedure = new AtomicReference<Future<?>>();
threadPool = Executors.newFixedThreadPool(1);
theProcedure = new Runnable() {
public void run() {
doProcedure();
}
};
}
private void doProcedure() {
// a bunch of stuff
updater.set(new Updater());
try {
SwingUtilities.invokeAndWait(updater.get());
} catch (InvocationTargetException | InterruptedException ex) {
}
// some more stuff
}
public void execute() {
try {
synchronized(this) {
if (null != currProcedure.get()) {
currProcedure.get().cancel(true);
}
if (null != updater.get()) {
updater.get().cancel();
}
}
currProcedure.set(threadPool.submit(theProcedure));
} catch (RejectedExecutionException ex) {
}
}
}
class Updater implements Runnable {
private AtomicBoolean cancelled;
public Updater() {
cancelled = new AtomicBoolean(false);
}
public void cancel() {
cancelled.set(true);
}
public void run() {
if (cancelled.get()) {
return;
}
// do the GUI update
}
}
它会像这样使用:
Procedure p = new Procedure();
p.execute();
p.execute();
实际上,我是从 AWT 事件线程调用 Procedure.execute()
的,所以我认为这可能与之有关;除此之外,我不知道我做错了什么,也不知道如何实现我的目标。有什么帮助吗?
编辑:我也尝试过cancel()
我的Updater
脱离AWT事件线程,但无论如何都没有运气:
if (null != currProcedure.get()) {
currProcedure.get().cancel(true);
}
if (null != updater.get()) {
threadPool.submit(new Runnable() {
public void run() {
updater.get().cancel();
}
});
}
编辑 2:我的日志记录似乎暗示我成功中断了 updater
(我收到 java.lang.InterruptedException
),但是run()
无论如何都会执行(并且 cancelled.get()
在其末尾仍然是 false
)。为什么 cancel(true)
不会停止 updater
的执行?
最佳答案
您的代码在多个方面已被破坏。你似乎期待AtomicReference
神奇地修复你的竞争条件,但顾名思义,它提供的只是对引用的原子访问。如果您多次访问该对象,您就会有多次访问,每个访问都是原子的,但加在一起仍然不是线程安全的。
第一个例子:
updater.set(new Updater());
try {
SwingUtilities.invokeAndWait(updater.get());
} catch (InvocationTargetException | InterruptedException ex) {
}
两者,set
以及以下get
调用是原子的,但谁说您将在 get
上收到引用信息?仍然是你拥有的那个set
之前?例如,您可能会收到更新的 Updater
由另一个线程调度的实例会导致出现 Updater
的情况从未发送到 EDT,而是发送到其他两次。
第二个例子:
if (null != currProcedure.get()) {
currProcedure.get().cancel(true);
}
if (null != updater.get()) {
updater.get().cancel();
}
同样的错误两次。您正在检查 AtomicReference.get()
的结果反对null
但不是-null
在那个调用中,谁说它仍然是非- null
在下一次调用时?您在 synchronized
中有该代码阻塞,但由于其他线程访问相同的变量,例如从内部doProcedure()
如果没有同步,它不提供任何保护。也许您从未重置对 null
的引用(这将是另一个错误)所以这里没有问题,但它清楚地表明了对如何使用 AtomicReference
的误解s。
此外,你说你是这样使用它的:
Procedure p = new Procedure();
p.execute();
p.execute();
所以你正在调用 execute
这些执行方法一个接一个地尝试取消它们在 updater
中找到的任何内容。 立即,但此时后台可能正在执行您的“一堆东西”,并且尚未设法设置其 updater
(如 Harald has pointed out )。所以两者execute
来电可能会看到null
或非常过时的Updater
实例,之后后台线程设置第一个 Updater
然后是第二个Updater
,其中没有一个被取消。请注意,后台线程的中断结束了invokeAndWait
的等待部分。但不会取消所提供的可运行对象的执行。
由于您正在使用AtomicReference
你应该开始真正使用它。原子更新是实现预期逻辑的关键功能,例如:
Updater newUpdater=new Updater();
Updater old=updater.getAndSet(newUpdater);
if(old!=null) old.cancel();
SwingUtilities.invokeLater(newUpdater);
通过读取旧的更新程序并自动设置新的更新程序,您可以确保每个新的 Updater
与旧的可能取消配对。通过将值保留在局部变量中而不是多次读取它们,您可以确保中间不会有更新更改引用。即使使用多个后台线程,这也适用,并且不需要额外的 synchronized
block 。
安排Updater
的电话已更改为invokeLater
,因为在单线程执行器等待Updater
完成的情况下意味着线程永远不会设置新的 Updater
并取消旧的。
请注意 Updater
本身应该设置 updater
引用null
在完成的时候。取消 Updater
是没有意义的。已经完成,但自 updater
引用是一个共享引用,其存在时间可能远远长于 Updater
的(预期)生命周期。它应该被清除,这样它就可以立即被垃圾收集,而不是躺在那里直到下一个 Updater
已设置。
关于java - 一旦我将 Runnable 传递给不同线程中的 Swing 的 invokeAndWait ,就无法停止它,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23398044/