我有一个带有固定线程池的类,我用它来多次运行一个过程。该过程的一部分涉及创建一个传递给Runnable
的{{1}}以更新我的Swing GUI。
我希望能够在发出再次运行过程的请求时抢占任何正在运行的过程。为此,我保留了SwingUtilities.invokeAndWait
来自最后一次Future
来电,因此我submit()
可以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
?
答案 0 :(得分:1)
对我来说,看起来问题就在于
// a bunch of stuff
当一堆东西被执行时,我们有以下情况
currProcedure
包含theProcedure
的第N次运行,而updater
仅包含Updater
的第(N-1)个st实例。因此,cancel
块中的两个synchronized(true)
调用不会定位一对匹配的项目。实际上,(N-1)st Updater
被取消了,但那个很长。另一方面,“一堆东西”可能并不关心在cancel(true)
上调用的Future
,所以一旦完成,“一堆东西”之后的行就会被阻止并安排下一个Updater
。
要解决这个问题,以下内容可能有所帮助。在“一堆东西”之后,输入以下代码:
synchronized(this) {
if (!Thread.currentThread().isInterrupted()) {
// copy the lines after "a bunch of stuff here to schedule the updater
}
}
在execute()
范围内,您无需费心取消Updater
。它不需要任何取消功能。
这两个synchronized
块现在已经对事件进行了正确的排序。新的execute()
首先使其进入同步块。然后,它可以抢占正在运行的doProcedure
来安排Updater
,或者doProcedure
首先使其同步。那么,Updater
值得运行,不是吗?必须有一个不归路。
我假设cancel(true)
Future
上的.interrupt()
最终为基础线程上的doProcedure
(当前不确定,请查看)。如果没有,你肯定会找到一种方法以某种方式中断{{1}}线程。
答案 1 :(得分:1)
您的代码在几个方面被破坏了。您似乎希望AtomicReference
能够神奇地修复您的竞争条件,但正如其名称所示,它提供的所有内容都是 atomic 对引用的访问权限。如果您多次访问该对象,则您有多次访问,每次访问都是原子的,但仍然不是线程安全的。
第一个例子:
updater.set(new Updater());
try {
SwingUtilities.invokeAndWait(updater.get());
} catch (InvocationTargetException | InterruptedException ex) {
}
set
和以下get
两个调用都是原子的,但谁说你将在get
上收到的引用仍然是set
之前的引用?例如,您可能会收到一个更新的Updater
实例,该实例由另一个线程调度,导致一个Updater
从未发送到EDT但另一个发送到EDT的情况。
第二个例子:
if (null != currProcedure.get()) {
currProcedure.get().cancel(true);
}
if (null != updater.get()) {
updater.get().cancel();
}
两次相同的错误。您正在检查AtomicReference.get()
对null
的结果,但在该调用中不是null
,并且在下一个null / em>调用?您在synchronized
块中有该代码,但由于其他线程访问相同的变量,例如在没有同步的doProcedure()
内,它不提供任何保护。也许你永远不会重置对null
的引用(这可能是另一个错误)所以这里没问题,但它清楚地表明了对如何使用AtomicReference
的误解。
此外,你说你正在使用它:
Procedure p = new Procedure();
p.execute();
p.execute();
所以你一个接一个地调用execute
这些执行方法尝试取消他们在updater
中立即找到的任何内容,但此时背景可能会执行你的“一堆东西”,并没有设法设置其updater
(作为Harald has pointed out)。因此,两个execute
调用可能会看到null
或一个非常过时的Updater
实例,之后 后台线程会设置第一个Updater
以及之后的第二个Updater
invokeAndWait
,它们都没有被取消。请注意,后台线程的中断会结束AtomicReference
的等待部分,但不会取消所提供的runnable的执行。
由于您使用的是Updater newUpdater=new Updater();
Updater old=updater.getAndSet(newUpdater);
if(old!=null) old.cancel();
SwingUtilities.invokeLater(newUpdater);
,因此您应该开始使用它。原子更新是实现预期逻辑的关键特性,例如:
Updater
通过阅读旧的更新程序并以原子方式设置新的更新程序,您可以确保每个新的synchronized
与可能取消旧的更新程序配对。通过将值保留在局部变量中而不是多次读取它们,可以确保在更改引用之间不存在中间更新。这甚至适用于多个后台线程,并且不需要额外的Updater
块。
调度invokeLater
的调用已更改为Updater
,因为在单个线程执行程序等待Updater
完成的情况下,暗示线程永远不会到来设置新的Updater
并取消旧的{。}。
请注意,updater
本身应在完成时将null
引用设置为Updater
。取消已经完成的updater
是没有意义的,因为Updater
引用是一个共享引用,可能存在的时间远远超过Updater
的{预期}生命周期被清除所以它可以立即被垃圾收集,而不是躺在那里,直到设置下一个{{1}}。