一. 初始化线程
每个程序都有一组线程作为应用程序逻辑开始的地方。在标准的程序中,只有一个这样的线程:这个线程调用程序类的main方法。在Applet初始化线程中有一个创建Applet对象以及调用Applet的init和start方法的线程;这些动作可能发生在单一线程上,也可能是两个或更多的不同线程上,这依赖与Java平台的具体实现。在这个教程中,我们称这些线程为初始化线程。
在Swing程序中,初始化线程不能有太多的事情做。他们最基本的工作就是去创建一个Runnable对象,这个Runnable对象用于初始化GUI以及在事件调度线程上执行对象的计划任务。一旦GUI被创建,主要通过GUI事件来驱动程序,所以应该在事件调度线程上引发每一个短任务的执行。应用程序代码可以在事件调度线程上完成附加的任务(如果他们完成的很快,那么就不需要用户界面上的事件处理)或者在一个工作者线程上(如果是长时间的任务)。
一个初始化线程通过调用javax.swing.SwingUtilities.invokeLater方法或者javax.swing.SwingUtilities.invokeAndWait方法完成GUI的创建任务。这两个方法都带有单一的参数:一个定义新任务的Runnable对象。他们仅仅通过名称来显示他们之间的区别:invokeLater简单的完成任务并返回;invokeAndWait则在返回之前必需等待任务执行完成。
你可以看这个贯穿Swing教程的例子:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
}
在applet中,GUI的创建任务必需从使用invokeAndWait的init方法中开始。否则,init方法可能在GUI被创建之前就返回了,这可能在web浏览器开始一个applet时引发问题。在一些其他类型的程序中,完成GUI创建的任务通常是初始化线程最后要做的事情,因此使用invokeLater还是invokeAndWait就变得无关紧要了。
为什么不在初始化线程本身中简单的创建GUI呢?因为几乎所有创建Swing组件或者与Swing组件有关系的代码都必需运行在事件调度线程上。这个约束的讨论将在下一节中进行。
二. 事件调度线程
Swing事件处理代码运行在一个特殊的线程上,就是通常所说的事件调度线程。大多数调用Swing方法的代码同样运行在这个线程上。这是必需的因为大多数Swing对象的方法并非“线程安全”:从多个线程中调用他们要冒着线程冲突或者内存不一致错误的风险。一些Swing组件方法在API规范中被标注为“线程安全”;这些组件方法可以从任何线程上被安全的调用。而所有其他的Swing组件方法必需从事件调度线程上被调用。虽然程序可能时常运行正常而忽略这个规则,但是易受到不可预知错误的影响而难以再用。
关于线程安全的说明:这种看似陌生而又是Java平台并非线程安全的一个重要方面。原来任何尝试创建一个线程安全的GUI类库都面临着一些根本的问题。关于这个问题的更多信息,请查看以下的关于Graham Hamilton的blog:多线程工具包:一个失败的梦想?
将一系列的短任务代码运行于事件调度线程上是一个很好的想法。大多数任务被事件处理方法调用,例如ActionListener.actionPerformed。利用invokeLater或者invokeAndWait方法可以通过应用程序代码来完成其他的任务。在事件调度线程上的任务必需被很快的完成;如果他们不是,那么未被处理的事件会倒退并且用户界面变的反应迟钝。
如果你需要测定你的代码是否运行在事件调度线程上,请调用javax.swing.SwingUtilities.isEventDispatchThread来进行测试。
三. 工作者线程和SwingWorker
当一个Swing程序需要执行一个长时间任务时,它通常使用工作者线程之一,也就是通常所说的后台线程。每个运行于工作者线程上的任务通过javax.swing.SwingWorker的实例来表现。SwingWorker本身是一个抽象类;为了创建一个SwingWorker对象你必需定义一个子类;匿名内部类常常具有创建非常简单的SwingWorker对象的用途。
SwingWorker提供了许多通讯和控制特性:
l SwingWorker的子类可以定义一个方法,done方法,这个方法在后台任务完成时自动的在事件调度线程上被调用。
l SwingWorker实现了java.util.concurrent.Future。这个接口允许后台任务提供一个返回值给另外一个线程。这个接口中的其他方法允许后台任务的取消以及探查后台任务是否已被完成或者已被取消。
l 后台任务可以通过调用SwingWorker.publish方法提供中间计算结果,并从事件调度线程上引起SwingWorker.process方法的调用。
l 后台任务可以定义约束属性。更改这些属性将触发事件,并从事件调度线程上引起事件处理方法的调用。
这些特性将在以下的子章节中被讨论。
注意:javax.swing.SwingWorker类被加入到Java SE 6平台中。在较早的版本中,另外一个类,同样被叫做SwingWorker,已经被广泛的用于一些相同的目的。老版本的SwingWorker并非Java平台规范的一部分,因此没有在JDK部分被提供。
新版本的SwingWorker是一个完整的新类。它的功能并非是老版本SwingWorker的一个严格意义上的扩展集。在这两个类中的方法虽然实现了相同的功能,但是并非拥有相同的名称。当javax.swing.SwingWorker的一个新实例需要各自的新后台任务时,老版本的SwingWorker类实例同样可被重复使用。
在整个Java指南中,任何一个关于SwingWorker的讨论现在只针对javax.swing.SwingWorker
1. 简单的后台任务
让我们以一个任务开始,这个任务虽然非常的简单,但是非常的耗费时间。TumbleItem小应用程序载入一组图形文件并将其用于一个动画中。如果从一个初始化线程中载入图形文件的话,那么在GUI出现之前可能会被阻塞。如果从一个事件调度线程中载入这些图形文件的话,那么GUI可能会暂时的失去响应。
为了避免这些问题,TumbleItem对象从它的初始化线程中创建和执行一个SwingWorker实例对象。该对象的doInBackground方法执行于一个工作者线程上,它载入图片并放入到一个ImageIcon类型的数组中,并且返回一个该数组的引用。然后done方法在事件调度线程中被执行,它调用get方法接收这个数组的引用,并赋值给applet类中的一个名叫imgs的属性,这个属性允许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() {
//Remove the "Loading images" label.
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是一个泛型类,带有2个类型参数。第一个类型参数为doInBackground方法指定一个返回类型,并且也为get方法指定一个返回类型,通过调用另外一个线程来接收由doInBackground方法返回的对象。当后台任务仍旧处于激活状态时,SwingWorker的第二个类型参数为中间计算结果指定一个返回类型。因为这个例子无需返回中间计算结果,Void可以被用来作为一个占位符。
你可能想知道设置imgs属性的代码是不是非得这样复杂。为什么要使doInBackground返回一个对象并且在done方法中接收它?为何不在刚才的doInBackground方法中直接设置imgs属性?这个问题的关键在于imgs对象在工作者线程中被创建并且在事件调度线程中被使用(译者注:工作在不同的线程中)。当对象通过这种方式在线程之间被共享时,你必需确保在一个线程中的更改可以被另外一个线程所访问。利用get方法可以保证这点,因为利用get方法可以创建代码之间的一种happen-before关系(译者注:happen-before关系是一种操作内存读写共享变量的锁机制,这种机制可以保证一个线程写入的结果对另一个线程的读取是可视的,详情请参考Java语言规范的第17章),代码之间的这种关系可以创建imgs对象并且代码可以使用它。
关于happen-before关系的更多信息,请参考在Concurrency教程中的内存一致性错误。
实际上有两个方法用于接收由doInBackground方法返回的对象。
l 调用不带参数的SwingWorker.get方法。如果后台任务没有完成,则get方法将阻塞直到后台任务完成。
l 调用带指定超时时间参数的SwingWorker.get方法。如果后台任务没有完成,则get方法将阻塞直到后台任务完成——除非超时首先过期,在这样的情况下,get方法抛出java.util.concurrent.TimeoutException异常。
在从事件调度线程中调用任何一个get的重载方法时都要当心,直到get方法返回,否则没有GUI事件将被处理并且GUI是“冻结”的。不要调用不带参数的get方法,除非你确信后台任务是完成的或者接近于完成。
关于TumbleItem例子的更多信息,请参考在使用其他Swing特性教程中的如何使用Swing计时器章节。
2. 有中间计算结果的任务
当后台任务仍旧处于工作状态时,它经常具有提供中间计算结果的用途。通过调用SwingWorker.publish方法,后台任务就可以实现这个功能。这个方法接受一个变长数目的参数。每个参数的类型必需由SwingWorker的第二个类型参数指定。
覆盖SwingWorker.process方法来收集由publish方法提供的计算结果。这个方法将从事件调度线程上被调用。对publish方法的多次调用所得到的计算结果经常被堆积在一个单一的process方法调用中。
让我们来看一个使用publish方法提供中间计算结果的Flipper例子。这个程序在后台任务中通过产生一系列的随机布尔值来测试java.util.Random类的公正性。这相当于抛硬币;因此我们称它Flipper。后台任务使用一个FlipPair类型的对象来报告它的计算结果。
private static class FlipPair {
private final long heads, total;
FlipPair(long heads, long total) {
this.heads = heads;
this.total = total;
}
}
heads属性表示随机布尔值为true的次数数目;total属性表示随机布尔值的总数目。
后台任务是通过FlipTask实例来表现:
private class FlipTask extends SwingWorker<Void, FlipPair> {
由于后台任务并非返回一个最终结果,所以第一个类型参数就变得无关紧要;Void被用来作为一个占位符。在每一次“抛硬币”之后,后台任务就调用publish方法。
@Override
protected Void doInBackground() {
long heads = 0;
long total = 0;
Random random = new Random();
while (!isCancelled()) {
total++;
if (random.nextBoolean()) {
heads++;
}
publish(new FlipPair(heads, total));
}
return null;
}
(isCancelled方法将在下一章节中讨论)因为publish方法被频繁的调用,所以在事件调度线程中大量的FlipPair对象可能被堆积在process方法被调用之前;process方法每次只报告最后一次的值,并用它来更新GUI界面:
protected void process(List pairs) {
FlipPair pair = pairs.get(pairs.size() - 1);
headsText.setText(String.format("%d", pair.heads));
totalText.setText(String.format("%d", pair.total));
devText.setText(String.format("%.10g",
((double) pair.heads)/((double) pair.total) - 0.5));
}
如果随机布尔值是公正的,那么当Flipper在运行的时候,在devText中显示的值应该越来越接近于0。
注释:setText方法在Flipper中的使用实际上是“线程安全”的,就象在它的规范中定义的那样。这意味着我们可以省略掉publish和process的处理方式而从工作者线程上直接设置文本域的值。为了提供一个简单的SwingWorker中间计算结果的示范,我们选择了忽略这个事实。
3. 取消执行中的后台任务
为了取消一个正在运行的后台任务,可以调用SwingWorker.cancel方法。后台任务必需与它自己的取消合作。这里有两种方法可以完成这件事情:
l 当它接收到一个中断时就终止。这个过程在Concurrency中的Interrupts章节中被描述。
l 常常通过调用SwingWorker.isCanceled方法。如果这个SwingWorker对象的取消已经被调用,那么这个方法返回true。
取消方法带有一个单一的布尔参数。如果这个参数为true,那么cancel方法发送一个中断给后台任务。无论参数是true还是false,调用cancel方法将把对象的取消状态设置为true。这个值可以通过isCanceled方法返回。一旦改变发生,取消状态将不能被改回。
上一小节的Flipper例子使用了“status-only” 习惯用语(译者注:status-only可能理解为在方法中仅仅处理对象的状态,这里的方法当然指doInBackground方法)。当isCancelled方法返回true时,将从doInBackground方法中退出主循环。这将发生在当用户点击“Cancel”按钮的时候,此时触发的代码将会调用带有一个false参数的cancel方法。
为Flipper例子使用status-only方法是明知的,因为SwingWorker.doInBackground方法的实现不包括任何的可能抛出InterruptedException异常的代码。为了对一个中断有回应,后台任务将不得不常常的调用Thread.isInterrupted方法。为了得到相同的目的也可以同样方便的使用SwingWorker.isCancelled方法。
注释:如果在一个SwingWorker对象的后台任务已经被取消之后再去调用get方法,那么java.util.concurrent.CancellationException异常将被抛出。
4. 约束属性和状态的方法
SwingWorker支持约束属性,这个功能具有与其他线程通讯的用途。有两个约束属性被预先定义:progress和state。与使用所有约束属性一样,progress和state可以被用来在事件调度线程上触发事件处理任务。
通过实现一个PropertyChangeListener监听器,程序可以用progress, state和其他约束属性来跟踪属性的变化。要了解更多的信息,请参考在编写事件监听器中的编写PropertyChangeListener监听器章节。
progress 约束变量(The progress Bound Variable)
progress 约束变量可以是一个从0到100的整型值。它有一个预先定义的设置器方法(protected SwingWorker.setProgress)和一个预先定义的获取器方法(public SwingWorker.getProgress)。
ProgressBarDemo例子从一个后台任务中使用progress属性来更新一个进度条控件。关于这个例子的详细讨论,请参考在使用Swing组件中的如何使用进度条章节。
state约束变量(The state Bound Variable)
state约束变量显示了SwingWorker在它的生命周期中所处的阶段。这个约束变量包含一个SwingWorker.StateValue类型的枚举值。可能的值为:
PENDING
从(SwingWorker)对象的创建直到doInBackground方法刚刚被调用之前的这段时间内的状态。
STARTED
从doInBackground方法被调用之前不久直到done方法被调用之前不久的这段时间内的状态。
DONE
(SwingWorker)对象存在的其余状态(译者注:也就是done方法被调用之前不久之后)。
当前state约束变量的值可以通过SwingWorker.getState方法返回。
状态方法(Status Methods)
有2个方法可以报告后台任务的当前状态,这2个方法是Future接口的一部分。正如你在取消后台任务中看到的,如果后台任务已经被取消,那么isCancelled方法返回true。另外,如果后台任务已经被完成,那么isDone方法返回true,因此要么是正常状态,要么是正在被取消状态。
分享到:
相关推荐
Java Swing 是一个用于构建桌面应用程序的用户界面工具包,它是Java Foundation Classes (JFC) 的一部分...对于初学者来说,通过研究提供的 "几个swing多线程的例子",可以更好地理解如何在实践中结合 Swing 和多线程。
例如,在Swing中,可以使用SwingWorker类,它提供了方便的方法来处理后台任务和进度更新。 总之,实现Java多线程进度条涉及线程同步、共享数据更新以及UI更新的协调。理解这些核心概念,并根据具体需求选择合适的...
综上所述,"javaswing 模拟QQ聊天"项目涵盖了Java Swing GUI编程、多线程通信、Socket网络编程等多个核心知识点,对于学习和提升Java桌面应用开发能力非常有帮助。开发者需要理解如何在Swing环境中组织和管理线程,...
### Java线程编程知识点概述 #### 一、书籍简介与目标读者 ...无论是初学者还是有一定经验的开发者,都能从中获得宝贵的信息和技术指导,进而提升自己的编程技能,并能够编写出更加快速、稳定和健壮的Java应用程序。
【描述】描述中提到,该程序虽然没有添加图形用户界面(GUI),但其核心的多线程下载逻辑是清晰易懂的,非常适合初学者作为学习参考。对于新手来说,理解并掌握多线程下载的概念和实现至关重要。多线程下载是将大...
Java Swing 是Java编程语言中用于构建桌面应用程序用户界面的一个库,它是Java...这个Java写的Swing界面系统提供了实践这些概念的机会,通过阅读和运行源代码,你可以深入了解Swing的工作原理,并提升你的GUI编程技能。
这个压缩包的代码示例可能会涵盖以上提到的一些或全部知识点,通过实际编写和运行这些代码,学习者可以深入理解Swing的工作原理,并逐渐掌握创建Java桌面应用的技能。在实践中不断探索和尝试,将有助于加深对Swing的...
8. **Swing工具包(Swing Utilities)**:学习使用Swing提供的各种实用工具类,如SwingWorker,以实现非阻塞的UI更新和后台任务处理。 9. **国际化与本地化**:理解如何使Swing应用支持多种语言和文化,以满足全球...
10. **高级特性**:Swing还支持拖放操作(Drag and Drop)、打印功能、以及SwingWorker类,用于在后台线程执行耗时任务,提高用户体验。 通过深入学习这个"JAVA_精通swing程序设计"的指南,你将能够熟练地利用Swing...
此外,Swing还支持高级特性如拖放操作、模态对话框、以及内置的SwingWorker类,用于在后台线程执行耗时任务,保持UI的响应性。 总之,Swing组件大全是一份宝贵的资源,无论你是初学者还是有经验的Java开发者,都...
10. **SwingWorker**:SwingWorker是用于在后台线程执行耗时任务的类,防止UI冻结。它支持进度报告和取消操作,使GUI保持响应性。 通过阅读《Java Swing(第二版)》的源码,开发者不仅可以深入理解Swing的工作原理,...
总的来说,"Swing星际争霸"项目展示了Java Swing在创建桌面游戏方面的应用潜力,为学习者提供了关于GUI编程、事件处理、多线程和游戏设计的实际案例。如果你对Java编程和游戏开发感兴趣,这是一个很好的实践项目,...
王鹏老师的Java Swing源码集合为学习和理解Swing提供了宝贵的资源,帮助开发者深入理解Swing组件的工作原理以及如何在实际项目中应用。 Swing 提供了丰富的组件集,包括按钮、文本框、滚动面板、菜单等,这些组件都...
生产者/消费者模型是一个多线程处理的模式,其中生产者线程生成数据,而消费者线程消费这些数据。在Java中,ArrayBlockingQueue和LinkedBlockingQueue是常用的生产者/消费者模型的基础,它们可以用来实现线程间的...
在实际项目中,Swing通常与其他Java库结合使用,如SwingWorker用于在后台线程执行耗时任务,避免阻塞UI。此外,Swing还与JavaFX协同工作,为开发者提供更现代的图形效果和动画。 总之,“深入浅出Java Swing程序...
本压缩包中的"Swing Examples"包含了多个Java Swing源代码示例,这些示例可以帮助你深入理解Swing的工作原理和用法。下面,我们将详细讨论一些关键的Swing知识点: 1. **JFrame**: JFrame是Swing中的顶级容器,用于...
综上所述,这个Java Swing小工具展示了如何结合Swing组件、文件I/O、事件处理和多线程等技术来实现文件复制功能。它的开源性质为初学者提供了一个学习和实践的平台,同时也为现有功能的扩展提供了可能。
此外,SWING的模型-视图-控制器(MVC)架构也得到了充分的阐述,这有助于理解组件的工作原理和如何实现复杂的用户交互。 布局管理器是SWING中的重要组成部分,它们负责控制组件在窗口中的排列方式。书中会介绍...
Swing和JavaFX都提供了异步编程模型,如SwingWorker和Task,用于在后台线程执行任务并在完成时更新UI。 8. **数据库操作**:在档案管理系统中,数据库交互可能涉及事务管理和连接池,这些都需要考虑线程安全。JDBC...
源代码包含的示例涵盖了Swing的基本用法到高级特性,如组件的定制、布局管理、事件处理、模型-视图-控制器(MVC)设计模式的应用,以及Swing中的线程管理等。通过这些实例,读者可以深入理解Swing的工作原理,并学会...