演示Swing与线程一起使用存在的问题。不断点击Bad按钮会抛出异常(没有使用事件分配线程),但不断点击Good按钮不会抛出异常(使用了事件分配线程)。
```java
import java.awt.EventQueue;
import java.util.Random;
import javax.swing.*;
/**
* 该程序演示了与事件并行运行的线程
*
* @author Cay Horstmann
* @version 1.24 2015-06-21
*/
public class SwingThreadTest {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new SwingThreadFrame();
frame.setTitle("SwingThreadTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
/**
* 该框架有两个按钮,用于填充来自单独线程的组合框。
* “Good”按钮使用事件队列,“Bad”按钮直接修改组合框
*
* @author Administrator
*/
class SwingThreadFrame extends JFrame {
public SwingThreadFrame() {
final JComboBox<Integer> combo = new JComboBox<>();
combo.insertItemAt(Integer.MAX_VALUE, 0);
combo.setPrototypeDisplayValue(combo.getItemAt(0));
combo.setSelectedIndex(0);
JPanel panel = new JPanel();
JButton goodButton = new JButton("Good");
goodButton.addActionListener(even -> new Thread(new GoodWorkerRunnable(combo)).start());
panel.add(goodButton);
JButton badButton = new JButton("Bad");
badButton.addActionListener(event -> new Thread(new BadWorkerRunnable(combo)).start());
panel.add(badButton);
panel.add(combo);
add(panel);
pack();
}
}
/**
* 此可运行项通过随机添加和删除数字来修改组合框。
* 这可能会导致错误,因为组合框方法不同步并且工作线程和事件分发线程都访问了组合框。
*/
class BadWorkerRunnable implements Runnable {
private JComboBox<Integer> combo;
private Random generator;
public BadWorkerRunnable(JComboBox<Integer> aCombo) {
combo = aCombo;
generator = new Random();
}
@Override
public void run() {
try {
while (true) {
int i = Math.abs(generator.nextInt());
if (i % 2 == 0) {
//在非事件分配线程中接触Swing组件是会出问题的
combo.insertItemAt(i, 0);
} else if (combo.getItemCount() > 0) {
combo.removeItemAt(i % combo.getItemCount());
}
Thread.sleep(1);
}
} catch (InterruptedException e) {
}
}
}
/**
* 此可运行项通过随机添加和删除数字来修改组合框。
* 为了确保组合框未损坏,编辑操作将转发到事件分发线程。
*
* @author Administrator
*/
class GoodWorkerRunnable implements Runnable {
private JComboBox<Integer> combo;
private Random generator;
public GoodWorkerRunnable(JComboBox<Integer> aCombo) {
combo = aCombo;
generator = new Random();
}
@Override
public void run() {
try {
while (true) {
//事件分配线程
EventQueue.invokeLater(() -> {
int i = Math.abs(generator.nextInt());
if (i % 2 == 0) {
//只能在事件分配线程中接触Swing组件
combo.insertItemAt(i, 0);
} else if (combo.getItemCount() > 0) {
combo.removeItemAt(i % combo.getItemCount());
}
});
Thread.sleep(1);
}
} catch (InterruptedException e) {
}
}
}
```
不断点击Bad按钮抛出异常的原因:当把一个元素插入组合框时,组合框将产生一个事件来更新显示。然后,显示代码开始运行,读取组合框当前的值并准备显示这个值。但是,工作器线程依然保持运行,有时候会造成组合框的值数目减少。但是显示代码依然认为组合框的值数目比实际的多,于是会访问不存在的值,触发ArrayIndexOutOfBounds异常。
<br/>
针对上面的这种情况,可以采用给组合框加锁来避免。但是,这样消耗更多的时间,而且程序员常被同步命令搞昏了头,经常编写出容易造成死锁的程序。所以这种方案并不可取。
- 网络通信
- 网络协议
- 端口和套接字
- TCP网络程序
- UDP网络程序
- 多线程聊天室
- 多线程
- 线程相关概念
- 线程实现方式
- 中断线程
- 线程生命周期
- 线程优先级
- 优先级规则
- 案例演示
- 线程同步机制
- 线程同步机制
- synchronized关键字
- ReentrantLock类
- Condition类
- 监视器概念
- volatile关键字
- final变量
- 死锁
- 线程局部变量
- 读/写锁
- 原子类
- 阻塞队列
- 工作规则
- 案例演示
- 常用阻塞队列
- 线程安全集合
- 高效的映射/集/队列
- 并发集视图
- 写数组的拷贝
- Arrays类的并行数组算法
- 同步包装器
- Callable与Future
- 执行器
- 线程池
- 预定执行
- 控制任务组
- Fork-Join框架
- 同步器
- 同步器
- 信号量
- CountDownLatch类
- CyclicBarrier类
- Exchanger类
- SynchronousQueue类
- 线程与Swing
- Swing与线程问题
- 两个原则
- Swing工作线程
- 单一线程规则
- 文件IO
- File类
- 文件输入输出
- ZIP压缩文件
- 集合
- 集合框架
- 集合接口
- 集合实现类
- 线程安全集合
- 集合算法
- 迭代器
- 集合排序
- JDBC
- JDBC是什么
- JDBC-ODBC桥
- JDBC驱动程序类型
- JDBC常用类与接口
- 数据库操作
- 连接数据库
- 增/删/改/查/预处理
- 事务
- 批处理
- commons-dbutils工具
- 安全问题
- Jedis
- 使用Jedis操作Redis数据库
- JSON转换
- 使用连接池
- 案例
- 单例破坏
- 单例定义
- 单例实现方式
- 懒汉式实现单例
- 饿汉式实现单例
- 单例破坏
- 类的单例破坏
- 枚举的单例破坏
- 克隆
- 克隆是什么
- 浅克隆
- 深克隆
- 注解
- 注解是什么
- 三大注解
- 内置注解
- 元注解
- 自定义注解
- NIO
- 相关概念
- BIO/NIO/AIO
- 多线程编程
- 线程同步
- 线程通信
- NIO
- NIO三大核心组件
- NIO网络编程
- NIO文件读写
- AIO
- Java8新特性
- Lambda表达式
- 方法引用
- 函数式接口
- 默认方法
- 什么是默认方法
- 默认方法语法格式
- 多个同名的默认方法问题
- 静态默认方法
- 默认方法实例
- Stream
- Stream是什么
- Stream示例
- Optional容器
- 新的日期时间API
- Base64
- SPI
- SPI是什么
- SPI与API的区别
- 常见场景
- 使用SPI需遵循的约定
- SPI使用步骤