4

我有一个使用 Java 线程不断执行某些操作的 Swing 应用程序。此操作的结果会更新 UI 中图形的内容:

class ExampleThread {
    ... 
    public void run() {
        while (running) {
            // extract some information
            ...

            // show results in UI
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                     // use information to update a graph
                }
            });

            // sleep some seconds
            ...
        }
    }
    ...
}

我遇到的问题是 EDT 在其他操作中臃肿。在这种情况下,ExampleThread可以注册Runnable更新图形的各种实例。对于我的应用程序,这将是浪费时间,因为图表会在显示结果之前多次更新。我想要的是//update a graph每个 EDT 周期最多运行一次代码。

我的问题是:确保 aRunnable只运行一次的正确方法是什么?

4

3 回答 3

5

原子脏标志应该可以解决问题。这具有仅在必要时才添加到 EDT 队列的额外好处。很大的假设 - 您保存提取信息的数据结构是线程安全的,但这必须是正确的,您的上面的代码也可以正常工作。如果不是,您应该考虑另一个答案中指出的 SwingWorker 方法;您还可以与脏标志结合使用以防止冗余更新。

AtomicBoolean dirty = new AtomicBoolean(false);
while (running) {
        // extract some information
        ...

        if (!dirty.getAndSet(true)) {            
          SwingUtilities.invokeLater(new Runnable() {
              public void run() {
                if (dirty.getAndSet(false)) {
                   // use information to update a graph
                }
              }
          });
        }

        // sleep some seconds
        ...
    }
于 2010-11-16T16:13:13.880 回答
3

我建议使用SwingWorker并利用发布方法将更新发布回 EDT。然后,EDT 将在下次安排时应用一个或多个更新。例如,假设您的计算结果是Integer实例:

// Boolean flag to determine whether background thread should continue to run.
AtomicBoolean running = new AtomicBoolean(true);

new SwingWorker<Void, Integer>() {
  public Void doInBackground() {
    while (running.get()) {
      // Do calculation
      Integer result = doCalculation();

      publish(result);
    }
  }

  protected void process(List<Integer> chunks) {
    // Update UI on EDT with integer results.
  }
}

另一种方法是创建一个实例,该实例在每次调度时Runnable简单地从结果中消费。Queue这类似于幕后SwingWorker操作方式,但为您提供了更多控制权,因为您可以控制Queue实现并避免永久持有SwingWorker池线程之一。

private final Queue<Integer> resultQueue = Collections.synchronizedList(new LinkedList<Integer>());
// Thread-safe queue to hold results.    

// Our re-usable Runnable to be run on the EDT.  Consumes all results from the result queue
// before releasing the lock, although could obviously change this to consume up to N results.
Runnable consumer = new Runnable() {
  public void run() {
    synchronized(resultQueue) {
      Integer result;

      while ((result = resultQueue.take()) != null) {
        // Update UI.
      }
    }
  }
}

...

// Business logic to run on background thread.  Conditionally schedules the EDT to run as required.
while (true) {
  Integer result = doCalculation();

  synchronized(resultQueue) {
    boolean scheduleEdt = resultQueue.isEmpty();
    // Queue was empty before this result is added so need to schedule the EDT to run again.

    resultQueue.add(result);
  }

  if (scheduleEdt) {
    SwingUtilities.invokeLater(consumer);
  } else {
    System.err.println("EDT already scheduled -> Avoiding unecessary reschedule.");
  }
}
于 2010-11-16T15:53:54.753 回答
0

或者,您可能想考虑使用javax.swing.Timer.

于 2010-11-16T16:01:44.000 回答