Swing应用程序员常见的错误是误用Swing事件调度线程(Event DispatchThread,EDT)。他们要么从非UI线程访问UI组件;要么不考虑事件执行顺序;要么不使用独立任务线程而在EDT线程上执行耗时任务,结果使编写的应用程序变得响应迟钝、速度很慢。耗时计算和输入/输出(IO)密集型任务不应放在SwingEDT上运行。发现这种问题的代码并不容易,但Java SE6提供了javax.swing.SwingWorker类,使修正这种代码变得更容易。

使用SwingWorker,程序能启动一个任务线程来异步查询,并马上返回EDT线程,允许EDT继续执行后续的UI事件。

        SwingWorker类帮你管理任务线程和EDT之间的交互,尽管SwingWorker不能解决并发线程中遇到的所有问题,但的确有助于分离SwingEDT和任务线程,使它们各负其责:对于EDT来说,就是绘制和更新界面,并响应用户输入;对于任务线程来说,就是执行和界面无直接关系的耗时任务和I/O密集型操作。

SwingWorker结构

SwingWoker实现了java.util.concurrent.RunnableFuture接口。RunnableFuture接口是Runnable和Future两个接口的简单封装。

因为实现了Runnable,所以有run方法,调用FutureTask.run()

因为实现了Future,所以有:

  1. public abstract class SwingWorker<T, V> implements RunnableFuture<T> {
  2. //FutureTask
  3. private final FutureTask<T> future;
  4. public final void run() {
  5. future.run();
  6. }
  7. public SwingWorker() {
  8. Callable<T> callable =
  9. new Callable<T>() {
  10. //1. 任务线程一创建就处于PENDING状态
  11. public T call() throws Exception {
  12. //2. 当doInBackground方法开始时,任务线程就进入STARTED状态
  13. setState(StateValue.STARTED);
  14. return doInBackground();
  15. }
  16. };
  17. //FutureTask
  18. future = new FutureTask<T>(callable) {
  19. @Override
  20. protected void done() {
  21. doneEDT();
  22. //3. 当doInBackground方法完成后,任务线程就处于DONE状态
  23. setState(StateValue.DONE);
  24. }
  25. };
  26. state = StateValue.PENDING;
  27. propertyChangeSupport = new SwingWorkerPropertyChangeSupport(this);
  28. doProcess = null;
  29. doNotifyProgressChange = null;
  30. }
  31. }

SwingWorker有两个类型参数:T及VT是doInBackground和get方法的返回类型;V是publish和process方法要处理的数据类型

SwingWorker实例不可复用,每次执行任务必须生成新的实例。

doInBackground和get和done

  1. //1、doInBackground
  2. protected abstract T doInBackground() throws Exception ;
  3. //2、get --不可重写
  4. public final T get() throws InterruptedException, ExecutionException {
  5. return future.get();
  6. }
  7. //3、done
  8. protected void done() {
  9. }
  10. /**
  11. * Invokes {@code done} on the EDT.
  12. */
  13. private void doneEDT() {
  14. Runnable doDone =
  15. new Runnable() {
  16. public void run() {
  17. done();
  18. }
  19. };
  20. //SwingWorker在EDT上激活done()
  21. if (SwingUtilities.isEventDispatchThread()) {
  22. doDone.run();
  23. } else {
  24. doSubmit.add(doDone);
  25. }
  26. }

doInBackground方法作为任务线程的一部分执行,它负责完成线程的基本任务,并以返回值来作为线程的执行结果。继承类须覆盖该方法并确保包含或代理任务线程的基本任务。不要直接调用该方法,应使用任务对象的execute方法来调度执行。

在获得执行结果后应使用SwingWorker的get方法获取doInBackground方法的结果。可以在EDT上调用get方法,但该方法将一直处于阻塞状态,直到任务线程完成。最好只有在知道结果时才调用get方法,这样用户便不用等待。为防止阻塞,可以使用isDone方法来检验doInBackground是否完成。另外调用方法get(longtimeout, TimeUnitunit)将会一直阻塞直到任务线程结束或超时。get获取任务结果的最好地方是在done方法内

在doInBackground方法完成之后,SwingWorker调用done方法。如果任务需要在完成后使用线程结果更新GUI组件或者做些清理工作,可覆盖done方法来完成它们。这儿是调用get方法的最好地方,因为此时已知道线程任务完成了,SwingWorker在EDT上激活done方法,因此可以在此方法内安全地和任何GUI组件交互

【例】

  1. SwingWorker testWorker = new SwingWorker<Icon , Void>(){
  2. @Override
  3. protected Icon doInBackground() throws Exception {
  4. Icon icon = retrieveImage(strImageUrl);
  5. return icon;
  6. }
  7. protected void done(){
  8. //没有必要用invokeLater!因为done()本身是在EDT中执行的
  9. SwingUtilities.invokeLater(new Runnable(){
  10. @Override
  11. public void run() {
  12. Icon icon= get();
  13. lblImage.setIcon(icon); //lblImage可通过构造函数传入
  14. }
  15. }
  16. //execute方法是异步执行,它立即返回到调用者。在execute方法执行后,EDT立即继续执行
  17. testWorker.execute();
  • 指定Icon作为doInBackground和get方法的返回类型
  • 因为并不产生任何中间数据,所以指定Void类型作为中间结果类型。

publish和process

SwingWorker在doInBackground方法结束后才产生最后结果,但任务线程也可以产生和公布中间数据。有时没必要等到线程完成就可以获得中间结果。

中间结果是任务线程在产生最后结果之前就能产生的数据。当任务线程执行时,它可以发布类型为V的中间结果,通过覆盖process方法来处理中间结果。

任务对象的父类会在EDT线程上激活process方法,因此在process方法中程序可以安全的更新UI组件

  1. //SwingWorker.publish
  2. protected final void publish(V... chunks) {
  3. synchronized (this) {
  4. if (doProcess == null) {
  5. doProcess = new AccumulativeRunnable<V>() {
  6. @Override
  7. public void run(List<V> args) {
  8. //调用process
  9. process(args);
  10. }
  11. @Override
  12. protected void submit() {
  13. doSubmit.add(this);
  14. }
  15. };
  16. }
  17. }
  18. doProcess.add(chunks);
  19. }
  20. //SwingWorker.process 在EDT中调用
  21. protected void process(List<V> chunks) {
  22. }

当从任务线程调用publish方法时,SwingWorker类调度process方法。有意思的是process方法是在EDT上面执行,这意味着可以同Swing组件和其模型直接交互。

例如可让publish处理Icon类型的数据;则doInBackground对应应该返回List<Icon>类型

使用publish方法来发布要处理的中间数据,当ImageSearcher线程下载缩略图时,它会随着下载而更新图片信息列表,还会发布每一批图像信息,以便UI能在图片数据到达时显示这些图片。

如果SwingWorker通过publish发布了一些数据,那么也应该实现process方法来处理这些中间结果,任务对象的父类会在EDT线程上激活process方法,因此在此方法中程序可以安全的更新UI组件。

【例】

  1. private void retrieveAndProcessThumbnails(List<ImageInfo> infoList) {
  2. for (int x=0; x <infoList.size() && !isCancelled(); ++x) {
  3. ImageInfo info = infoList.get(x);
  4. String strImageUrl = String.format("%s/%s/%s_%s_s.jpg",
  5. IMAGE_URL, info.getServer(), info.getId(), info.getSecret());
  6. Icon thumbNail = retrieveThumbNail(strImageUrl);
  7. info.setThumbnail(thumbNail);
  8. //发布中间结果
  9. publish(info);
  10. setProgress(100 * (x+1)/infoList.size());
  11. }
  12. }
  13. /**
  14. * Process is called as a result of this worker thread's calling the
  15. * publish method. This method runs on the event dispatch thread.
  16. *
  17. * As image thumbnails are retrieved, the worker adds them to the
  18. * list model.
  19. *
  20. */
  21. @Override
  22. protected void process(List<ImageInfo> infoList) {
  23. for(ImageInfo info: infoList) {
  24. if (isCancelled()) { //见下节
  25. break;
  26. }
  27. //处理中间结果
  28. model.addElement(info);
  29. }
  30. }

cancel和isCancelled

  1. public final boolean cancel(boolean mayInterruptIfRunning) {
  2. return future.cancel(mayInterruptIfRunning);
  3. }
  4. /**
  5. * {@inheritDoc}
  6. */
  7. public final boolean isCancelled() {
  8. return future.isCancelled();
  9. }

如果想允许程序用户取消任务,实现代码要在SwingWorker子类中周期性地检查取消请求。调用isCancelled方法来检查是否有取消请求。检查的时机主要是:

  • doInBackground方法的子任务在获取每个缩略图之前
  • process方法中在更新GUI列表模型之前
  • done方法中在更新GUI列表模型最终结果之前

【例】判断是否被取消(见上例)

【例】取消

可以通过调用其cancel方法取消SwingWorker线程

  1. private void searchImages(String strSearchText, int page) {
  2. if (searcher != null && !searcher.isDone()) {
  3. // Cancel current search to begin a new one.
  4. // You want only one image search at a time.
  5. //检查现有线程是否正在运行,如果正在运行则调用cancel来取消
  6. searcher.cancel(true);
  7. searcher = null;
  8. }
  9. ...
  10. // Provide the list model so that the ImageSearcher can publish
  11. // images to the list immediately as they are available.
  12. searcher = new ImageSearcher(listModel, API_KEY, strEncodedText, page);
  13. searcher.addPropertyChangeListener(listenerMatchedImages);
  14. progressMatchedImages.setIndeterminate(true);
  15. // Start the search!
  16. searcher.execute();
  17. // This event thread continues immediately here without blocking.
  18. }

setProgress和getProgress

任务对象有一个进度属性,随着任务进展时,可以将这个属性从0更新到100标识任务进度。当你在任务实例内处理这些信息时,你可以调用setProgress方法来更新这个属性。

当该属性发生变化时,任务通知处理器进行处理。(?)

【例】

  1. //javax.imageio.ImageReader reader
  2. reader.addIIOReadProgressListener(new IIOReadProgressListener() {
  3. ...
  4. public void imageProgress(ImageReader source, float percentageDone) {
  5. setProgress((int) percentageDone);
  6. }
  7. public void imageComplete(ImageReader source) {
  8. setProgress(100);
  9. }
  10. });

客户端调用1、更新进度条事件处理

  1. /**
  2. * ProgressListener listens to "progress" property changes
  3. in the SwingWorkers that search and load images.
  4. */
  5. class ProgressListener implements PropertyChangeListener {
  6. // Prevent creation without providing a progress bar.
  7. private ProgressListener() {}
  8. ProgressListener(JProgressBar progressBar) {
  9. this.progressBar = progressBar;
  10. this.progressBar.setValue(0);
  11. }
  12. public void propertyChange(PropertyChangeEvent evt) {
  13. String strPropertyName = evt.getPropertyName();
  14. if ("progress".equals(strPropertyName)) {
  15. progressBar.setIndeterminate(false);
  16. int progress = (Integer)evt.getNewValue();
  17. progressBar.setValue(progress);
  18. }
  19. }
  20. private JProgressBar progressBar;
  21. }

客户端调用2、添加监听

    1. private void listImagesValueChanged(ListSelectionEvent evt) {
    2. ...
    3. ImageInfo info = (ImageInfo) listImages.getSelectedValue();
    4. String id = info.getId();
    5. String server = info.getServer();
    6. String secret = info.getSecret();
    7. // No need to search an invalid thumbnail image
    8. if (id == null || server == null || secret == null) {
    9. return;
    10. }
    11. String strImageUrl = String.format(IMAGE_URL_FORMAT, server, id, secret);
    12. retrieveImage(strImageUrl);
    13. ...
    14. }
    15. private void retrieveImage(String imageUrl) {
    16. // SwingWorker,不可复用
    17. ImageRetriever imgRetriever = new ImageRetriever(lblImage, imageUrl);
    18. progressSelectedImage.setValue(0);
    19. // Listen for changes in the "progress" property.
    20. // You can reuse the listener even though the worker thread will be a new SwingWorker.
    21. imgRetriever.addPropertyChangeListener(listenerSelectedImage);
    22. progressSelectedImage.setIndeterminate(true);
    23. // Tell the worker thread to begin with this asynchronous method.
    24. imgRetriever.execute();
    25. // This event thread continues immediately here without blocking.
    26. }

【Java线程】SwingWorker的用法的更多相关文章

  1. java 线程池的用法

    1.java自带的类ExecutorService用于提供线程池服务,可以一下方法初始化线程池: ExecutorService pool = Executors.newFixedThreadPool ...

  2. 四种Java线程池用法解析

    本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...

  3. 在Java 线程中返回值的用法

    http://icgemu.iteye.com/blog/467848 在Java 线程中返回值的用法 博客分类: Java Javathread  有时在执行线程中需要在线程中返回一个值:常规中我们 ...

  4. Java 四种线程池的用法分析

    1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { ...

  5. Java线程池理解及用法

    前言 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担.线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory ...

  6. 面试题:四种Java线程池用法解析 !=!=未看

    1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? 1 2 3 4 5 6 7 8 new Thread(new Runnable() {     @Override ...

  7. 【Java线程】Callable和Future

    Future模式 Future接口是Java线程Future模式的实现,可以来进行异步计算. Future模式可以这样来描述: 我有一个任务,提交给了Future,Future替我完成这个任务.期间我 ...

  8. 第23章 java线程通信——生产者/消费者模型案例

    第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...

  9. [转]Java线程安全总结

    最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣.已经拟好了提纲,大概分为这几个主题: java线程安全,java垃圾收集,java并发包详细介绍,java profi ...

  10. Java线程新特征——Java并发库

    一.线程池   Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定 ...

随机推荐

  1. linux中mycat的配置,分片,以及主从复制

    1.1    安装环境 1.jdk:要求jdk必须是1.7及以上版本 2.Mysql:推荐mysql是5.5以上版本 1.2  安装步骤 Mycat有windows.linux多种版本.本教程为lin ...

  2. Object C学习笔记6-如何在Windows环境搭建Object C开发环境

    1. 安装编译环境 Object C和其他很多语言一样,都需要有一个编译器.Object C 是在GCC下编译的.GCC(GNU Compiler Collection,GNU编译器集合),是一套由 ...

  3. 2018年美国大学生数学建模竞赛(MCM/ICM) D题解题思路

    首先整个赛题是一道集选址,优化,评价,预测的综合性赛题,对于任务 1,包括三个小问题,第一是有望完全电动化,那么就需要评价什么叫完全电动化,所以先建立一个基本的标准,比如人车比例达到多少.需要多少充电 ...

  4. 180813-Spring之RestTemplate使用小结一

    Spring之RestTemplate使用小结 作为一个Java后端,需要通过HTTP请求其他的网络资源可以说是一个比较常见的case了:一般怎么做呢? 可能大部分的小伙伴直接捞起Apache的Htt ...

  5. Ubuntu 16.04安装tensorflow_gpu的方法

    参考资料: Ubuntu 16.04安装tensorflow_gpu 1.9.0的方法 装Tensorflow,运行项目报错: module compiled against API version ...

  6. phpcmsv9广告版位调用方法

    <div class="ya"> <?php // pc:get 使用sql语句获取指定条件的广告版位! ?> {pc:get sql="SELE ...

  7. Table 组件构建过程中遇到的问题与解决思路

    在 GearCase 开源项目构建 Table 组件的过程中.遇到了各式各样的问题,最后尝试了各种方法去解决这些问题. 遇到的部分问题 checkbox 的全选和半选问题 table 组件的排序请求方 ...

  8. zookeeper_节点数据版本号问题

    转自:Simba_cheng 更新节点数据的方法: 同步方法:Stat setData(final String path, byte data[], int version) 异步方法:void s ...

  9. windows docker 安装cloudera/quickstart

    最近需要写一个大数据的项目,但是公司没有测试环境,真是cao蛋,没办法,只能自己搭建一个测试环境,所以就在本地电脑装一个cloudera/quickstart,这个是一个单节点的大数据平台, 是clo ...

  10. iOS 静态库生成(引用第三方SDK、开源库、资源包)

    一.静态库创建 打开Xcode, 选择File ----> New ---> Project  选择iOS ----> Framework & Library ---> ...