本教程适用于JDK 8。本页中描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
有关Java SE 9及后续版本中更新的语言功能的摘要,请参阅Java语言更改。
有关所有JDK版本的新功能、增强功能以及已删除或已弃用选项的信息,请参阅JDK发行说明。
让我们从一个非常简单但潜在耗时的任务开始。 applet加载了一组用于动画的图形文件。如果图形文件从初始线程加载,GUI可能会有延迟才会出现。如果图形文件从事件分派线程加载,GUI可能会暂时无响应。TumbleItem
为了避免这些问题,TumbleItem从其初始线程创建并执行一个SwingWorker实例。该对象的doInBackground方法在工作线程中执行,将图像加载到ImageIcon数组中,并返回对它的引用。然后done方法在事件分派线程中执行,调用get来检索此引用,并将其分配给名为imgs的applet类字段。这样TumbleItem可以立即构建GUI,而无需等待图像加载完成。
这是定义和执行SwingWorker对象的代码。
SwingWorker worker = new SwingWorker<ImageIcon[], Void>() {
@Override
public ImageIcon[] doInBackground() {
final ImageIcon[] innerImgs = new ImageIcon[nimgs];
for (int i = 0; i < nimgs; i++) {
innerImgs[i] = loadImage(i+1);
}
return innerImgs;
}
@Override
public void done() {
//移除“正在加载图像”标签。
animator.removeAll();
loopslot = -1;
try {
imgs = get();
} catch (InterruptedException ignore) {}
catch (java.util.concurrent.ExecutionException e) {
String why = null;
Throwable cause = e.getCause();
if (cause != null) {
why = cause.getMessage();
} else {
why = e.getMessage();
}
System.err.println("Error retrieving file: " + why);
}
}
};
SwingWorker的所有具体子类都实现doInBackground;done的实现是可选的。
请注意,SwingWorker是一个泛型类,有两个类型参数。第一个类型参数为doInBackground指定了一个返回类型,也为get方法指定了一个返回类型,其他线程可以通过调用get方法来检索doInBackground返回的对象。SwingWorker的第二个类型参数指定了在后台任务仍然活动时返回的中间结果的类型。由于此示例不返回中间结果,因此使用Void 作为占位符。
你可能会想知道设置imgs的代码是否过于复杂。为什么要让doInBackground返回一个对象,并使用done来获取它?为什么不直接让doInBackground直接设置imgs呢?问题是imgs引用的对象是在工作线程中创建的,并在事件分发线程中使用。当对象在这种方式下在线程之间共享时,必须确保在一个线程中所做的更改对另一个线程可见。使用get可以保证这一点,因为使用get会在创建imgs的代码和使用它的代码之间创建一个happens before关系。有关happens before关系的更多信息,请参考内存一致性错误的并发教程。
实际上有两种方法可以获取doInBackground返回的对象。
SwingWorker.get没有参数的方法。如果后台任务没有完成,get会阻塞直到完成。SwingWorker.get带有指定超时参数的方法。如果后台任务没有完成,get会阻塞直到完成,除非超时时间先过期,此时get会抛出java.util.concurrent.TimeoutException异常。在从事件分发线程调用get的时候要小心;在get返回之前,没有GUI事件被处理,GUI界面会"冻结"。除非你确信后台任务已经完成或接近完成,否则不要调用没有参数的get。
有关TumbleItem示例的更多信息,请参考教程如何使用Swing定时器在课程使用其他Swing功能中。