本文将讨论并发机制在Swing编程中的应用。
谨慎地使用并发机制对Swing开发人员来说非常重要。一个好的Swing程序使用并发机制来创建不会失去响应的用户接口-不管是什么样的用户交互,程序总能够对其给出响应。创建一个有响应的程序,开发人员必须学会如何在Swing框架中使用多线程。
一个Swing开发人员将会与下面几类线程打交道:
[1]Initial threads(初始线程),此类线程将执行初始化应用代码。
[2]The event dispatch thread(事件派发线程),所有的事件处理代码在这里执行。大多数与Swing框架交互的代码也必须执行这个线程。
[3]Worker threads(工作线程),也称作background threads(后台线程),此类线程将执行所有消耗时间的任务。
开发人员不需要在代码中显式的创建这些线程:它们是由runtime或Swing框架提供的。开发人员的工作就是利用这些线程来创建具有响应的,持久的Swing程序。
如同所有其他在Java平台上运行的程序,一个Swing程序可以创建额外的线程和线程池,这需要使用本文即将介绍的方法。本文将介绍以上这三种线程。工作线程的讨论将涉及到使用javax.swing.SwingWorker类。这个类有许多有用的特性,包括在工作线程任务与其他线程任务之间的通信与协作。
1.初始线程
每个程序都会在应用逻辑开始时生成一系列的线程。在标准的程序中,只有一个这样的线程:这个线程将调用程序主类中的main方法。在applet中初始线程是applet对象的构造子,它将调用init方法;这些actions可能在一个单一的线程中执行,或在两个或三个不同的线程中,这些都依据Java平台的具体实现。在本文中,我们称这类线程为初始线程(initial threads)。
在Swing程序中,初始线程没有很多事情要做。它们最基本的任务是创建一个Runnable对象,用于初始化GUI以及为那些用于执行事件派发线程中的事件的对象编排顺序。一旦GUI被创建,程序将主要由GUI事件驱动,其中的每个事件驱动将引起一个在事件派发线程中事件的执行。程序代码可以编排额外的任务给事件驱动线程(前提是它们会被很快的执行,这样才不会干扰事件的处理)或创建工作线程(用于执行消耗时间的任务)。
一个初始线程编排GUI创建任务是通过调用javax.swing.SwingUtilities.invokeLater或javax.swing.SwingUtilities.invokeAndWait。这两个方法都带有一个唯一的参数:Runnable用于定义新的任务。它们唯一的区别是:invokerLater仅仅编排任务并返回;invokeAndWait将等待任务执行完毕才返回。
看下面示例:
SwingUtilities.invokeLater(new Runnable()) {
public void run() {
createAndShowGUI();
}
}
|
在applet中,创建GUI的任务必须被放入init方法中并且使用invokeAndWait;否则,初始过程将有可能在GUI创建完之前完成,这样将有可能出现问题。在其他的情况下,编排GUI创建任务通常是初始线程中最后一个被执行的,所以使用invokeLater或invokeAndWait都可以。
为什么初始线程不直接创建GUI?因为几乎所有的用于创建和交互Swing组件的代码必须在事件派发线程中执行。这个约束将在下文中讨论。
2.事件派发线程
Swing事件的处理代码在一个特殊的线程中执行,这个线程被称为事件派发线程。大部分调用Swing方法的代码都在这个线程中被执行。这样做是必要的,因为大部分Swing对象是“非线程安全的”。
可以将代码的执行想象成在事件派发线程中执行一系列短小的任务。大部分任务被事件处理方法调用,诸如ActionListener.actionPerformed。其余的任务将被程序代码编排,使用invokeLater或invokeAndWait。在事件派发线程中的任务必须能够被快速执行完成,如若不然,未经处理的事件被积压,用户界面将变得“响应迟钝”。
如果你需要确定你的代码是否是在事件派发线程中执行,可调用javax.swing.SwingUtilities.isEventDispatchThread。
3.工作线程与SwingWorker
当一个Swing程序需要执行一个长时间的任务,通常将使用一个工作线程来完成。每个任务在一个工作线程中执行,它是一个javax.swing.SwingWorker类的实例。SwingWorker类是抽象类;你必须定义它的子类来创建一个SwingWorker对象;通常使用匿名内部类来这做这些。
SwingWorker提供一些通信与控制的特征:
[1]SwingWorker的子类可以定义一个方法,done。当后台任务完成的时候,它将自动的被事件派发线程调用。
[2]SwingWorker类实现java.util.concurrent.Future。这个接口允许后台任务提供一个返回值给其他线程。该接口中的方法还提供允许撤销后台任务以及确定后台任务是被完成了还是被撤销的功能。
[3]后台任务可以通过调用SwingWorker.publish来提供中间结果,事件派发线程将会调用该方法。
[4]后台任务可以定义绑定属性。绑定属性的变化将触发事件,事件派发线程将调用事件处理程序来处理这些被触发的事件。
4.简单的后台任务
下面介绍一个示例,这个任务非常简单,但它是潜在地消耗时间的任务。TumbleItem applet导入一系列的图片文件。如果这些图片文件是通过初始线程导入的,那么将在GUI出现之前有一段延迟。如果这些图片文件是在事件派发线程中导入的,那么GUI将有可能出现临时无法响应的情况。
为了解决这些问题,TumbleItem类在它初始化时创建并执行了一个StringWorker类的实例。这个对象的doInBackground方法,在一个工作线程中执行,将图片导入一个ImageIcon数组,并且返回它的一个引用。接着done方法,在事件派发线程中执行,得到返回的引用,将其放在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是一个范型类,有两个参数。第一个类型参数指定doInBackground的返回类型。同时也是get方法的类型,它可以被其他线程调用以获得来自于doInBackground的返回值。第二个类型参数指定中间结果的类型,这个例子没有返回中间结果,所以设为void。
使用get方法,可以使对象imgs的引用(在工作线程中创建)在事件派发线程中得到使用。这样就可以在线程之间共享对象。
实际上有两个方法来得到doInBackground类返回的对象。
[1]调用SwingWorker.get没有参数。如果后台任务没有完成,get方法将阻塞直到它完成。
[2]调用SwingWorker.get带参数指定timeout。如果后台任务没有完成,阻塞直到它完成-除非timeout期满,在这种情况下,get将抛出java.util.concurrent.TimeoutException。
5.具有中间结果的任务
让一个正在工作的后台任务提供中间结果是很有用处的。后台任务可以调用SwingWorker.publish方法来做到这个。这个方法接受许多参数。每个参数必须是由SwingWorker的第二个类型参数指定的一种。
可以覆盖(override)SwingWorker.process来保存由publish方法提供的结果。这个方法是由事件派发线程调用的。来自publish方法的结果集通常是由一个process方法收集的。
我们看一下Filpper.java提供的实例。这个程序通过一个后台任务产生一系列的随机布尔值测试java.util.Random。就好比是一个投硬币试验。为了报告它的结果,后台任务使用了一个对象FlipPair。
private static class FlipPair {
private final long heads, total;
FlipPair(long heads, long total) {
this.heads = heads;
this.total = total;
}
}
|
heads表示true的结果;total表示总的投掷次数。
后台程序是一个FilpTask的实例:
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;
}
|
由于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));
}
|
6.取消后台任务
调用SwingWorker.cancel来取消一个正在执行的后台任务。任务必须与它自己的撤销机制一致。有两个方法来做到这一点:
[1]当收到一个interrupt时,将被终止。
[2]调用SwingWorker.isCanceled,如果SwingWorker调用cancel,该方法将返回true。
7.绑定属性和状态方法
SwingWorker支持bound properties,这个在与其他线程通信时很有作用。提供两个绑定属性:progress和state。progress和state可以用于触发在事件派发线程中的事件处理任务。
通过实现一个property change listener,程序可以捕捉到progress,state或其他绑定属性的变化。
7.1The progress Bound Variable
Progress绑定变量是一个整型变量,变化范围由0到100。它预定义了setter (the protected SwingWorker.setProgress)和getter (the public SwingWorker.getProgress)方法。
7.2The state Bound Variable
State绑定变量的变化反映了SwingWorker对象在它的生命周期中的变化过程。该变量中包含一个SwingWorker.StateValue的枚举类型。可能的值有:
[1]PENDING
这个状态持续的时间为从对象的建立知道doInBackground方法被调用。
[2]STARTED
这个状态持续的时间为doInBackground方法被调用前一刻直到done方法被调用前一刻。
[3]DONE
对象存在的剩余时间将保持这个状态。
需要返回当前state的值可调用SwingWorker.getState。
7.3Status Methods
两个由Future接口提供的方法,同样可以报告后台任务的状态。如果任务被取消,isCancelled返回true。此外,如果任务完成,即要么正常的完成,要么被取消,isDone返回true。
分享到:
相关推荐
实验中的“源码”部分可能包含了使用Java Swing或AWT创建GUI,并结合多线程技术实现的示例代码。而“要求”文件可能列出了实验的目标、步骤、预期结果和评估标准。通过实践这些实验,学生将深入理解Java多线程和GUI...
使用SwingUtilities.invokeLater()或SwingWorker可以保证UI操作在正确的线程中执行。 6. **设计模式**:生产者-消费者模式是一个经典并发模式,用于处理数据的生产和消费。在这种模式中,生产者生成数据放入缓冲区...
Java Swing线程是Java GUI应用程序开发中的核心概念,特别是在创建响应迅速...通过使用`SwingUtilities.invokeLater()`和`SwingWorker`等工具,可以有效地管理并发,防止阻塞Swing线程,从而提高应用的性能和用户体验。
- **事件驱动模型**:Java中的GUI框架如AWT和Swing采用了事件驱动模型,其中有一个专门的事件线程负责处理用户的输入事件。为了避免事件处理导致UI卡顿,通常会将耗时的操作放入到单独的线程中执行。 - **...
因此,开发者可能使用SwingWorker或者JavaFX的Task来在后台线程处理计算密集型任务,然后在事件调度线程更新GUI。 总的来说,"JAVA编写的多线程小弹球测试"项目涵盖了Java多线程、GUI编程、随机数生成和颜色处理等...
在这个抽奖项目中,可能会使用`SwingWorker`,它是Swing专门为在后台执行耗时操作并更新UI设计的类。`SwingWorker`的`doInBackground()`方法用于执行抽奖逻辑,而`done()`方法则用来更新UI,显示抽到的奖项。 抽奖...
在Java编程中,"Action"通常指的是`java.util.concurrent.Action`接口或`javax.swing.ActionEvent`类,它们在多线程环境中被广泛使用。线程安全是多线程编程中的一个关键概念,它涉及到一个对象在被多个线程并发访问...
考虑到学生选课可能会有并发操作,系统可能需要使用多线程来提高性能和响应速度。例如,当学生提交选课请求时,可以在后台线程中处理该请求,避免阻塞用户界面。Swing提供了SwingWorker类,它是专为更新Swing组件...
同时,为了更新用户界面,可能使用了Swing的异步事件调度模型,如SwingUtilities.invokeLater()或SwingWorker,确保在事件调度线程中安全地修改UI组件。 总结起来,"Java Swing 赛马小游戏 线程实例"涵盖了以下关键...
- 使用 SwingWorker 类进行异步任务,如查询车票信息,确保UI响应流畅。 6. **异常处理**: - 程序应捕获并处理可能出现的异常,如数据库连接错误、无效用户输入等,以提供友好的错误提示。 7. **设计模式**: ...
- 使用`SwingWorker`类处理后台任务。 10. **第10章:线程组**(Thread Groups) - 线程组的概念及其用途; - 如何管理和控制线程组; - 线程组与系统资源管理的关系。 ##### 第二部分:高级技术与模式 1. **...
- **解决方案**: 使用`SwingWorker`类或`ExecutorService`进行异步任务处理,确保GUI组件更新操作在EDT中执行。 **2. 典型案例分析** - **银行账户转账**: 分析多线程环境下账户余额的更新问题。 - 问题: 如果多...
为了在多线程中同步进度更新,我们可以利用synchronized关键字、wait()、notify()方法,或者使用Java并发库中的高级工具,如Semaphore、CyclicBarrier或CountDownLatch等。 一个简单的进度条实现可以采用共享变量...
为了更新GUI,我们需要使用Swing的并发机制,比如SwingWorker,因为它可以在后台线程中执行计算,并在事件调度线程中安全地更新UI。 对于计时功能,我们可以在新的线程中不断累加当前时间,直到达到或超过用户设定...
在这个项目中,开发者利用Java的多线程能力来处理并发用户交互,Socket用于网络通信,而Swing则提供了图形用户界面(GUI)。 首先,我们来看Java的多线程。在多线程环境下,程序可以同时执行多个任务,这对于聊天...
例如,在Java Swing或Android开发中,可以使用`SwingWorker`或`AsyncTask`类来实现这种异步更新。当耗时任务完成后,我们可以发送一个信号(如设置共享变量或调用`notify()`)来唤醒进度条线程,让它知道任务已完成...
因此,开发者需要理解Swing的并发模型,合理使用SwingWorker进行后台任务处理。 10. **Swing应用程序框架**:为了简化开发,Swing还提供了AbstractAction和ActionCommand等概念,以及像JFrame的...
为了解决这个问题,可以采用Swing提供的SwingWorker类或者ExecutorService来实现后台线程处理,从而确保UI的流畅性。 #### 七、结论 Java线程的强大功能和灵活性使其成为处理并发问题的重要工具。无论是提高用户...
Swing是Java编程语言中的一个图形用户界面(GUI)工具包,它是Java Foundation Classes (JFC)的一部分,主要用于创建桌面应用程序。在"Swing实现购票源码"中,我们可以推测这是一份利用Swing来构建的模拟火车票购买...
为了避免UI线程被阻塞,我们需要使用`SwingWorker`或者`javax.swing.Timer`来异步更新进度。 具体实现步骤如下: 1. 创建一个`Runnable`实现类,负责下载任务。这个类需要维护当前的下载位置、总大小等信息,并...