本教程适用于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功能中。