【Java线程】SwingWorker的用法
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,所以有:
- public abstract class SwingWorker<T, V> implements RunnableFuture<T> {
- //FutureTask
- private final FutureTask<T> future;
- public final void run() {
- future.run();
- }
- public SwingWorker() {
- Callable<T> callable =
- new Callable<T>() {
- //1. 任务线程一创建就处于PENDING状态
- public T call() throws Exception {
- //2. 当doInBackground方法开始时,任务线程就进入STARTED状态
- setState(StateValue.STARTED);
- return doInBackground();
- }
- };
- //FutureTask
- future = new FutureTask<T>(callable) {
- @Override
- protected void done() {
- doneEDT();
- //3. 当doInBackground方法完成后,任务线程就处于DONE状态
- setState(StateValue.DONE);
- }
- };
- state = StateValue.PENDING;
- propertyChangeSupport = new SwingWorkerPropertyChangeSupport(this);
- doProcess = null;
- doNotifyProgressChange = null;
- }
- }
SwingWorker有两个类型参数:T及V。T是doInBackground和get方法的返回类型;V是publish和process方法要处理的数据类型
SwingWorker实例不可复用,每次执行任务必须生成新的实例。
doInBackground和get和done
- //1、doInBackground
- protected abstract T doInBackground() throws Exception ;
- //2、get --不可重写
- public final T get() throws InterruptedException, ExecutionException {
- return future.get();
- }
- //3、done
- protected void done() {
- }
- /**
- * Invokes {@code done} on the EDT.
- */
- private void doneEDT() {
- Runnable doDone =
- new Runnable() {
- public void run() {
- done();
- }
- };
- //SwingWorker在EDT上激活done()
- if (SwingUtilities.isEventDispatchThread()) {
- doDone.run();
- } else {
- doSubmit.add(doDone);
- }
- }
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组件交互。
【例】
- SwingWorker testWorker = new SwingWorker<Icon , Void>(){
- @Override
- protected Icon doInBackground() throws Exception {
- Icon icon = retrieveImage(strImageUrl);
- return icon;
- }
- protected void done(){
- //没有必要用invokeLater!因为done()本身是在EDT中执行的
- SwingUtilities.invokeLater(new Runnable(){
- @Override
- public void run() {
- Icon icon= get();
- lblImage.setIcon(icon); //lblImage可通过构造函数传入
- }
- }
- //execute方法是异步执行,它立即返回到调用者。在execute方法执行后,EDT立即继续执行
- testWorker.execute();
- 指定Icon作为doInBackground和get方法的返回类型
- 因为并不产生任何中间数据,所以指定Void类型作为中间结果类型。
publish和process
SwingWorker在doInBackground方法结束后才产生最后结果,但任务线程也可以产生和公布中间数据。有时没必要等到线程完成就可以获得中间结果。
中间结果是任务线程在产生最后结果之前就能产生的数据。当任务线程执行时,它可以发布类型为V的中间结果,通过覆盖process方法来处理中间结果。
任务对象的父类会在EDT线程上激活process方法,因此在process方法中程序可以安全的更新UI组件。
- //SwingWorker.publish
- protected final void publish(V... chunks) {
- synchronized (this) {
- if (doProcess == null) {
- doProcess = new AccumulativeRunnable<V>() {
- @Override
- public void run(List<V> args) {
- //调用process
- process(args);
- }
- @Override
- protected void submit() {
- doSubmit.add(this);
- }
- };
- }
- }
- doProcess.add(chunks);
- }
- //SwingWorker.process 在EDT中调用
- protected void process(List<V> chunks) {
- }
当从任务线程调用publish方法时,SwingWorker类调度process方法。有意思的是process方法是在EDT上面执行,这意味着可以同Swing组件和其模型直接交互。
例如可让publish处理Icon类型的数据;则doInBackground对应应该返回List<Icon>类型
使用publish方法来发布要处理的中间数据,当ImageSearcher线程下载缩略图时,它会随着下载而更新图片信息列表,还会发布每一批图像信息,以便UI能在图片数据到达时显示这些图片。
如果SwingWorker通过publish发布了一些数据,那么也应该实现process方法来处理这些中间结果,任务对象的父类会在EDT线程上激活process方法,因此在此方法中程序可以安全的更新UI组件。
【例】
- private void retrieveAndProcessThumbnails(List<ImageInfo> infoList) {
- for (int x=0; x <infoList.size() && !isCancelled(); ++x) {
- ImageInfo info = infoList.get(x);
- String strImageUrl = String.format("%s/%s/%s_%s_s.jpg",
- IMAGE_URL, info.getServer(), info.getId(), info.getSecret());
- Icon thumbNail = retrieveThumbNail(strImageUrl);
- info.setThumbnail(thumbNail);
- //发布中间结果
- publish(info);
- setProgress(100 * (x+1)/infoList.size());
- }
- }
- /**
- * Process is called as a result of this worker thread's calling the
- * publish method. This method runs on the event dispatch thread.
- *
- * As image thumbnails are retrieved, the worker adds them to the
- * list model.
- *
- */
- @Override
- protected void process(List<ImageInfo> infoList) {
- for(ImageInfo info: infoList) {
- if (isCancelled()) { //见下节
- break;
- }
- //处理中间结果
- model.addElement(info);
- }
- }
cancel和isCancelled
- public final boolean cancel(boolean mayInterruptIfRunning) {
- return future.cancel(mayInterruptIfRunning);
- }
- /**
- * {@inheritDoc}
- */
- public final boolean isCancelled() {
- return future.isCancelled();
- }
如果想允许程序用户取消任务,实现代码要在SwingWorker子类中周期性地检查取消请求。调用isCancelled方法来检查是否有取消请求。检查的时机主要是:
- doInBackground方法的子任务在获取每个缩略图之前
- process方法中在更新GUI列表模型之前
- done方法中在更新GUI列表模型最终结果之前
【例】判断是否被取消(见上例)
【例】取消
可以通过调用其cancel方法取消SwingWorker线程
- private void searchImages(String strSearchText, int page) {
- if (searcher != null && !searcher.isDone()) {
- // Cancel current search to begin a new one.
- // You want only one image search at a time.
- //检查现有线程是否正在运行,如果正在运行则调用cancel来取消
- searcher.cancel(true);
- searcher = null;
- }
- ...
- // Provide the list model so that the ImageSearcher can publish
- // images to the list immediately as they are available.
- searcher = new ImageSearcher(listModel, API_KEY, strEncodedText, page);
- searcher.addPropertyChangeListener(listenerMatchedImages);
- progressMatchedImages.setIndeterminate(true);
- // Start the search!
- searcher.execute();
- // This event thread continues immediately here without blocking.
- }
setProgress和getProgress
任务对象有一个进度属性,随着任务进展时,可以将这个属性从0更新到100标识任务进度。当你在任务实例内处理这些信息时,你可以调用setProgress方法来更新这个属性。
当该属性发生变化时,任务通知处理器进行处理。(?)
【例】
- //javax.imageio.ImageReader reader
- reader.addIIOReadProgressListener(new IIOReadProgressListener() {
- ...
- public void imageProgress(ImageReader source, float percentageDone) {
- setProgress((int) percentageDone);
- }
- public void imageComplete(ImageReader source) {
- setProgress(100);
- }
- });
客户端调用1、更新进度条事件处理
- /**
- * ProgressListener listens to "progress" property changes
- in the SwingWorkers that search and load images.
- */
- class ProgressListener implements PropertyChangeListener {
- // Prevent creation without providing a progress bar.
- private ProgressListener() {}
- ProgressListener(JProgressBar progressBar) {
- this.progressBar = progressBar;
- this.progressBar.setValue(0);
- }
- public void propertyChange(PropertyChangeEvent evt) {
- String strPropertyName = evt.getPropertyName();
- if ("progress".equals(strPropertyName)) {
- progressBar.setIndeterminate(false);
- int progress = (Integer)evt.getNewValue();
- progressBar.setValue(progress);
- }
- }
- private JProgressBar progressBar;
- }
客户端调用2、添加监听
- private void listImagesValueChanged(ListSelectionEvent evt) {
- ...
- ImageInfo info = (ImageInfo) listImages.getSelectedValue();
- String id = info.getId();
- String server = info.getServer();
- String secret = info.getSecret();
- // No need to search an invalid thumbnail image
- if (id == null || server == null || secret == null) {
- return;
- }
- String strImageUrl = String.format(IMAGE_URL_FORMAT, server, id, secret);
- retrieveImage(strImageUrl);
- ...
- }
- private void retrieveImage(String imageUrl) {
- // SwingWorker,不可复用
- ImageRetriever imgRetriever = new ImageRetriever(lblImage, imageUrl);
- progressSelectedImage.setValue(0);
- // Listen for changes in the "progress" property.
- // You can reuse the listener even though the worker thread will be a new SwingWorker.
- imgRetriever.addPropertyChangeListener(listenerSelectedImage);
- progressSelectedImage.setIndeterminate(true);
- // Tell the worker thread to begin with this asynchronous method.
- imgRetriever.execute();
- // This event thread continues immediately here without blocking.
- }
【Java线程】SwingWorker的用法的更多相关文章
- java 线程池的用法
1.java自带的类ExecutorService用于提供线程池服务,可以一下方法初始化线程池: ExecutorService pool = Executors.newFixedThreadPool ...
- 四种Java线程池用法解析
本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...
- 在Java 线程中返回值的用法
http://icgemu.iteye.com/blog/467848 在Java 线程中返回值的用法 博客分类: Java Javathread 有时在执行线程中需要在线程中返回一个值:常规中我们 ...
- Java 四种线程池的用法分析
1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { ...
- Java线程池理解及用法
前言 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担.线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory ...
- 面试题:四种Java线程池用法解析 !=!=未看
1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? 1 2 3 4 5 6 7 8 new Thread(new Runnable() { @Override ...
- 【Java线程】Callable和Future
Future模式 Future接口是Java线程Future模式的实现,可以来进行异步计算. Future模式可以这样来描述: 我有一个任务,提交给了Future,Future替我完成这个任务.期间我 ...
- 第23章 java线程通信——生产者/消费者模型案例
第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...
- [转]Java线程安全总结
最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣.已经拟好了提纲,大概分为这几个主题: java线程安全,java垃圾收集,java并发包详细介绍,java profi ...
- Java线程新特征——Java并发库
一.线程池 Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定 ...
随机推荐
- Redis学习之路(二)之Redis入门基础
一.Redis基本介绍 (1)Redis介绍 Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件. 它支持多种类型的数据结构,如 字符串(string ...
- [SDOI2018]战略游戏 圆方树,树链剖分
[SDOI2018]战略游戏 这题是道路相遇(题解)的升级版,询问的两个点变成了\(S\)个点. LG传送门 还是先建出圆方树,考虑对于询问的\(S\)个点,答案就是圆方树上能包含这些点的最小连通块中 ...
- linux提权 searchsploit 使用规范
使用 searchsploit 时,要把整个控制台最大化,这样才能显示完整的漏洞信息. 查看漏洞帮助文件:
- android prgoressBar setProgressDrawable 在4.0系统式正常,在2.3系统上不能正常使用的问题
上次在做一个电池电量的进度显示时,需要根据背景主题色来切换电池电量的进度的颜色, 但是在对prgoressBar的setProgressDrawable进行设置之后发现,在4.0系统上能够正常,而在2 ...
- 关于判断用户输入的是不是int类型,这次没有正则表达式
末尾没有目的地的出租车,污点证人禁止入内!!! 不同的尝试有不同的方法 关于int类型的判断,我尝试了这么一个方法,可行,只是笨 正则表达式我没有搞清楚,没办法给大家讲解,欢迎各位明白人讲解,或者是我 ...
- 【转】将Centos的yum源更换为国内的阿里云源
摘要: 阿里云是最近新出的一个镜像源.得益于阿里云的高速发展,这么大的需求,肯定会推出自己的镜像源. 阿里云Linux安装镜像源地址:http://mirrors.aliyun.com/ CentOS ...
- Node JS World
Node JS World Environment tested on Ubuntu Install nvm/node/npm/yarn nvm : node version manager node ...
- Memached、Redis、Mongodb的区别
性能 • 性能都很高,redis和memached差不多 > Mongodb 操作 • Memached:数据结构单一,只有key/value数据结构 • Redis有五种数据类型 ...
- Python3的深拷贝和浅拷贝
a = 1 b = a a = 2 print(a, b) print(id(a), id(b)) """ 运行结果 2 1 1445293568 1445293536 ...
- Mysql启动失败解决方案 - 个人经验可能不适合所有场景
以前一直用的Mysql5.5,安装程序是一个exe程序,安装完了相应的服务也给我注册好了,然后直接启动连接即可. 最近升级到了8.0.15,发现和以前不一样了. 8.0.15下载地址 安装解压之后目录 ...