我有一个Swing应用程序,它使用Java Thread来不断执行某些操作。此操作的结果更新了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
实例。对于我的应用程序,由于图表将在显示结果之前多次更新,因此将浪费时间。我想要的是每个EDT周期最多运行一次//update a graph
代码。
我的问题是:确保Runnable
只运行一次的正确方法是什么?
答案 0 :(得分: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
...
}
答案 1 :(得分: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.");
}
}
答案 2 :(得分:0)
或者,您可能希望使用javax.swing.Timer
。