java - 一旦我将 Runnable 传递给不同线程中的 Swing 的 invokeAndWait ,就无法停止它

标签 java multithreading swing future executorservice

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

相关文章:

java - 在Java中动态设置JTextField中的文本

java - 没有 STOMP 和 SockJS 的带有 Spring 的基本 websocket

ios - 在后台线程上删除 Realm 对象会阻塞主线程

java - NoSuchMethodError : tools. 计时器。<init>(J)V

Java jLabel.setText() 方法不使用 String 重载

Java 改变 JPanel 的形状

java - AlertDialog : After showing the dialog, 背景 Canvas 渲染继续,但对话框永远不会关闭

java - 如何使用 QueryDSL 在 Spring Data JPA 中使用 order by 和 Limit

java - 使用 selenium webdriver 关注最近打开的选项卡

multithreading - Camel 并行处理选项