JavaSwing_5.4: 多线程并发与线程安全
2017-10-29 23:42
387 查看
本文链接: http://blog.csdn.net/xietansheng/article/details/78389265
Java Swing 图形界面开发(目录)
前面的所有 Swing 组件案例代码,为了简单重点介绍组件,都直接在主线程中直接创建并显示,实际开发中这将会存在线程安全隐患。
通常 Swing 不是线程安全的。除非另行说明,否则所有 Swing 组件及其相关类都必须保证在同一个线程(事件调度线程)中进行访问。
添加任务到事件调度线程:
创建窗口的正确方式:
对于耗时的任务,可以创建子线程在子线程中执行耗时操作,执行完后再提交任务到事件调度线程操作UI。为此,Swing 提供了 SwingWorker 来处理耗时任务,并更好的支持了与事件调度线程之间的通信。
SwingWorker 是一个抽象类,设计用于需要在后台线程中运行长时间运行任务的情况,并可在完成后或者在处理过程中向 UI 提供数据并更新。SwingWorker 的子类必须实现 doInBackground() 方法,在该方法中执行后台耗时计算。
SwingWorker 抽象类:
SwingWorker 任务执行相关方法:
SwingWorker 状态判断方法:
SwingWorker 后台任务执行中与事件调度线程通信相关方法:
与事件调度线程通信的另一种方式,通过设置属性改变监听器:
SwingWorker 的简单应用:
SwingWorker 线程间通信的应用:
结果展示:
Java Swing 图形界面开发(目录)
1. 概述
官方JavaDocsApi: Swing’s Threading Policy前面的所有 Swing 组件案例代码,为了简单重点介绍组件,都直接在主线程中直接创建并显示,实际开发中这将会存在线程安全隐患。
通常 Swing 不是线程安全的。除非另行说明,否则所有 Swing 组件及其相关类都必须保证在同一个线程(事件调度线程)中进行访问。
2. 事件调度线程(Event Dispatching Thread)
AWT 中有一个先进先出(FIFO)的事件队列(EventQueue)单例,添加到该队列中的任务(Runnable)将按顺序逐一在同一线程中被执行,该线程被称为 事件调度线程。Swing 组件也延用了该队列实例,所有 Swing 组件的创建、修改、绘制、响应输入都必须要添加到该事件队列中执行。组件注册的各种监听器,回调方法也是被添加到事件队列中执行,即所有监听器的回调方法中就处于事件调度线程,可以直接操作UI。添加任务到事件调度线程:
EventQueue.invokeLater(Runnable doRun); // 或者 SwingUtilities.invokeLater(Runnable doRun); // 上面两者的区别: EventQueue 在 AWT 包中,SwingUtilities 在 Swing 包中,后者内部实际上是直接又调用了前者。
创建窗口的正确方式:
package com.xiets.swing; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class Main { public static void main(String[] args) { // 此处处于 主线程,提交任务到 事件调度线程 创建窗口 SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // 此处处于 事件调度线程 createGUI(); } } ); } public static void createGUI() { // 此处处于 事件调度线程 JFrame jf = new JFrame("测试窗口"); jf.setSize(300, 300); jf.setLocationRelativeTo(null); jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); JPanel panel = new JPanel(); JButton btn = new JButton("Btn"); btn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // 此处处于 事件调度线程(所有监听器的回调都在 事件调度线程 中回调) } }); jf.setContentPane(panel); jf.setVisible(true); } }
3. 工作线程(SwingWorker)
上面所说的事件调度线程负责 UI 的创建、修改、绘制 和 响应输入等,因此在该线程中不能执行耗时的操作(网络IO、密集计算处理),否则将阻塞事件调度线程调度任何其他任务,造成应用程序对用户输入无反应。对于耗时的任务,可以创建子线程在子线程中执行耗时操作,执行完后再提交任务到事件调度线程操作UI。为此,Swing 提供了 SwingWorker 来处理耗时任务,并更好的支持了与事件调度线程之间的通信。
SwingWorker 是一个抽象类,设计用于需要在后台线程中运行长时间运行任务的情况,并可在完成后或者在处理过程中向 UI 提供数据并更新。SwingWorker 的子类必须实现 doInBackground() 方法,在该方法中执行后台耗时计算。
SwingWorker 抽象类:
/** * 类型参数: * T - 后台耗时计算结果的类型,即此 SwingWorker 的 doInBackground() 和 get() 方法返回的结果类型 * V - 用于保存此 SwingWorker 的 publish() 和 process() 方法的中间结果的类型 */ public abstract class SwingWorker<T, V> implements RunnableFuture<T> { ... public SwingWorker() { ... } ... protected abstract T doInBackground() throws Exception; ... }
SwingWorker 任务执行相关方法:
// 此方法将在一个线程池中被执行,在此方法中执行耗时操作,并把结果返回,之后通过 get() 方法可获取返回的结果 protected abstract T doInBackground() // 开始启动执行该任务,调用后将在线程池中执行 doInBackground 方法,此方法只能被调用一次。 void execute() // 被回调的方法: doInBackground 方法完成后,在事件指派线程 上执行此方法。 protected void done() // 如有必要,等待计算完成,然后获取 doInBackground 返回的结果。 T get() // 试图取消对此任务的执行。 boolean cancel(boolean mayInterruptIfRunning)
SwingWorker 状态判断方法:
// 如果在任务正常完成前将其取消,则返回 true。 boolean isCancelled() // 如果任务已完成,则返回 true。 boolean isDone() // 获取当前任务的状态,返回的是枚举,包含3个状态: 初始化(PENDING)、已启动(STARTED)、已完成(DONE) SwingWorker.StateValue getState();
SwingWorker 后台任务执行中与事件调度线程通信相关方法:
// 将数据块发送给 process(List<V> chunks) 方法。 protected void publish(V... chunks) // 被回调的方法: 在事件指派线程 上异步地从 publish 方法接收数据块。 protected void process(List<V> chunks)
与事件调度线程通信的另一种方式,通过设置属性改变监听器:
// 添加属性改变监听器(监听器方法将在事件调度线程中被回调) void addPropertyChangeListener(PropertyChangeListener listener) // 移除属性监听器 void removePropertyChangeListener(PropertyChangeListener listener) //(在后台任务线程中)发布指定属性的值改变事件,通知监听器回调 void firePropertyChange(String propertyName, Object oldValue, Object newValue) // 设置 progress 属性值,如果该值被改变,内部将自动发布 progress 属性改变事件 protected void setProgress(int progress); // SwingWorker 生命周期过程中还会自动发布 state 属性事件,state 的属性值无法手动设置, // 将根据事件生命周期自动改变并发布事件,也可通过 getState() 获取。
SwingWorker 的简单应用:
package com.xiets.swing; import javax.swing.*; public class Main { public static void main(String[] args) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { createGUI(); } } ); } public static void createGUI() { JFrame jf = new JFrame("测试窗口"); jf.setSize(300, 300); jf.setLocationRelativeTo(null); jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); JPanel panel = new JPanel(); final JLabel label = new JLabel(); label.setText("正在计算"); panel.add(label); jf.setContentPane(panel); jf.setVisible(true); // 创建后台任务 SwingWorker<String, Object> task = new SwingWorker<String, Object>() { @Override protected String doInBackground() throws Exception { // 此处处于 SwingWorker 线程池中 // 延时 5 秒,模拟耗时操作 Thread.sleep(5000); // 返回计算结果 return "Hello"; } @Override protected void done() { // 此方法将在后台任务完成后在事件调度线程中被回调 String result = null; try { // 获取计算结果 result = get(); } catch (Exception e) { e.printStackTrace(); } label.setText("结算结果: " + result); } }; // 启动任务 task.execute(); } }
SwingWorker 线程间通信的应用:
package com.xiets.swing; import javax.swing.*; import java.awt.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.List; public class Main { public static void main(String[] args) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { createGUI(); } } ); } public static void createGUI() { JFrame jf = new JFrame("测试窗口"); jf.setSize(300, 300); jf.setLocationRelativeTo(null); jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); JPanel panel = new JPanel(new BorderLayout()); final JLabel label = new JLabel("正在下载: 0%"); panel.add(label, BorderLayout.NORTH); final JTextArea textArea = new JTextArea(); panel.add(textArea, BorderLayout.CENTER); jf.setContentPane(panel); jf.setVisible(true); // 创建后台任务 SwingWorker<String, Integer> task = new SwingWorker<String, Integer>() { @Override protected String doInBackground() throws Exception { for (int i = 0; i < 100; i += 10) { // 延时模拟耗时操作 Thread.sleep(1000); // 设置 progress 属性的值(通过属性改变监听器传递数据到事件调度线程) setProgress(i); // 通过 SwingWorker 内部机制传递数据到事件调度线程 publish(i); } // 返回计算结果 return "下载完成"; } @Override protected void process(List<Integer> chunks) { // 此方法在 调用 doInBackground 调用 public 方法后在事件调度线程中被回调 Integer progressValue = chunks.get(0); textArea.append("已下载: " + progressValue + "%\n"); } @Override protected void done() { // 此方法将在后台任务完成后在事件调度线程中被回调 String result = null; try { // 获取计算结果 result = get(); } catch (Exception e) { e.printStackTrace(); } label.setText(result); textArea.append(result); } }; // 添加属性改变监听器 task.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if ("progress".equals(evt.getPropertyName())) { Object progressValue = evt.getNewValue(); label.setText("正在下载: " + progressValue + "%"); } } }); // 启动任务 task.execute(); } }
结果展示:
相关文章推荐
- Java并发01:进程、线程、并发、并行、多线程、线程安全、死锁、并发优缺点
- Java多线程基础(并发、线程安全、同步、互斥)
- C#转Java之路之三:多线程并发容器即线程安全的容器
- java并发实战第六章(2)非阻塞式线程安全列表与一般List集合多线程情况下的比较
- java多线程高并发线程安全问题
- Java学习笔记—多线程(java.util.concurrent并发包概括,转载)
- java多线程学习7-线程安全问题
- JAVA多线程与并发学习总结
- 【Java多线程与并发库】18.java线程面试题1
- JAVA多线程与并发学习总结分析
- Java多线程与并发库高级应用之线程数据交换Exchanger
- JAVA多线程和并发
- Java static 静态方法 并发(是否线程安全)
- Java并发和多线程(一)基础知识
- 微博 Qzone 微信 JAVA多线程和并发
- Java多线程 阻塞队列和并发集合
- Java 多线程并发学习资料
- java多线程并发(一)Semaphore,volatile,synchronized ,Lock, CyclicBarrier和CountDownLatch
- Java多线程之并发协作生产者消费者设计模式JDK1.5.0+升级优化版
- Java并发编程规则:设计线程安全的类