一旦我将其传递给另一个线程中的Swing的invokeAndWait,就无法阻止Runnable

时间:2014-04-30 21:08:27

标签: java multithreading swing future executorservice

我有一个带有固定线程池的类,我用它来多次运行一个过程。该过程的一部分涉及创建一个传递给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

2 个答案:

答案 0 :(得分:1)

对我来说,看起来问题就在于

// a bunch of stuff

当一堆东西被执行时,我们有以下情况

  1. currProcedure包含theProcedure的第N次运行,而
  2. updater仅包含Updater的第(N-1)个st实例。
  3. 因此,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}}。