【问题标题】:Using ExecutorService and ProgressMonitor in a Thread Safe Manner以线程安全的方式使用 ExecutorService 和 ProgressMonitor
【发布时间】:2013-12-24 10:21:00
【问题描述】:

我先定义 ProgressMonitor:

progressMonitor = new ProgressMonitor(parent, "Starting processing ...", "", 0, maxNumberProcesses+1);
progressMonitor.setProgress(0);

并且在同一个线程上使用 ExecutorService 和 invokeAll() 来处理 Callables 列表:

ExecutorService execService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); // use all available processors at startup
execService.invokeAll(callables); // wait for all tasks to complete
execService.shutdownNow(); // free thread pool resources

每个 Callable 的格式为:

class Callable implements Callable<List<String>>
{
    public List<String> call()
    {
        List<String> files = doSomeStuff();
        progressBarUpdate();
        return files;
    }
}

即;每个 Callable 调用 progressBarUpdate():

private void progressBarUpdate()
{
    if (progressMonitor != null)
    {
        Lock lock = new ReentrantLock();
        lock.lock();
        try
        {
            progressMonitor.increment();
        }
        finally
        {
            lock.unlock(); // release lock
        }
    }
}

每个 doSomeStuff() 都有自己的异常处理,如果发生错误或抛出异常,则返回空值。这就是为什么返回类型是 List 并且在这种情况下返回 null 的原因。 Callables 和它们返回的文件列表之间没有交叉,它们都在那里维护自己的文件列表。

我发现它工作正常,但偶尔会抛出以下形式的 InterruptedException:

Disposal was interrupted:
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:503)
at java.awt.EventQueue.invokeAndWait(EventQueue.java:1263)
at java.awt.Window.doDispose(Window.java:1209)
at java.awt.Dialog.doDispose(Dialog.java:1196)
at java.awt.Window.dispose(Window.java:1147)
at javax.swing.ProgressMonitor.close(ProgressMonitor.java:311)
at javax.swing.ProgressMonitor.setProgress(ProgressMonitor.java:264)

显示当达到监视器最大值时 setProgress() 调用 close():

public void setProgress(int nv) {
    if (nv >= max) {
        close();
    }
...

并且 close() 包含许多其他非线程安全调用。

我已经修改了我的代码,使得条件 nv>=max 不满足,并且我在 invokeAll() 之后显式调用 ProgressMonitor.close(),但我仍然不相信这种方法是完全线程安全的。

有没有其他人遇到过这种情况并找到了可靠的解决方案?

谢谢

格雷厄姆

PS。请注意,ProgressMonitor 不是 Swing 小部件,而是封装了 Swing 组件。因此,我确保 ProgressMonitor 不在 EDT 上运行。

【问题讨论】:

  • progressBarUpdate() 属于哪个类?还有,它是如何接收 progressMonitor 的?
  • 它与 ExecutorService 和 Callables 列表是同一类的成员。 ProgressMonitor 是一个字段,因为它被所有 Callables 调用。

标签: java multithreading concurrency executorservice progressmonitor


【解决方案1】:

如果您想执行后台任务并显示进度,您应该使用SwingWorkerSwingWorker 有一个 progress 属性,您可以在上面收听。它确保进度更新在 Event Dispatch Thread 中完成,而任务在后台线程中完成。

例如:

SwingWorker<?,?> task = ...;
final JProgressBar progressBar = new JProgressBar(0, 100);

task.addPropertyChangeListener(
        new PropertyChangeListener() {
            public  void propertyChange(PropertyChangeEvent evt) {
                 if ("progress".equals(evt.getPropertyName())) {
                     progressBar.setValue((Integer)evt.getNewValue());
            }
        }
 });

完整的示例代码在SwingWorker的javadoc中。

【讨论】:

  • 我正在使用一组 Callables 和一个不在 EDT 上运行的 ExecutorService。 Swing ProgressMonitor 同样是一个非 Swing 组件。
【解决方案2】:

看起来这是导致interrupt的原因:

Worker Thread (Callable1):
close() -> doDispose() -> EventQueue.invokeAndWait() {
synchronized (lock) {
            Toolkit.getEventQueue().postEvent(event);
            lock.wait(); // --> (2) blocked window disposal event gets interrupted
        }
}

ExecutorService.shutdownNow() :
 try {
                for (Worker w : workers) {
                    w.interruptNow(); // (1) --> Setting interrupt flag
                }
            } catch (SecurityException se) { // Try to back out
                runState = state;
                // tryTerminate() here would be a no-op
                throw se;
            }

从功能上看,当您的进度监视器完成(或到达max)时,在事件被调度之前(可能发生也可能不会发生取决于 eventQ 的繁忙程度),服务关闭本身。

从概念上讲,我在您的代码中看不到任何具体问题,但主要是您的执行程序服务与AWT.EventQueue 进行隐式线程通信。

您可以在progressMonitor.increment(); 周围使用InterruptedException 或在调用shutdownNow 之前使用ExecutorService.awaitTermination

【讨论】:

  • 感谢您的回复。我调用了invokeAll(),它等待所有任务完成并且可能不需要调用awaitTermination()。
  • 是的,我错过了invokeAll 调用但是这可以再次调用中断future.cancel 以防出现任何错误,然后返回。你如何处理Callable.call 方法中的错误,你能更新你的问题吗?
猜你喜欢
  • 1970-01-01
  • 2011-08-30
  • 2010-12-14
  • 2011-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多