简介
开发swing程序时,某些操作可能会比较耗时,如:
- 请求http接口获取数据后才能渲染
- 实现打字机效果渲染耗时较大(chat gpt效果)
如果不进行特殊处理,这些耗时操作将会导致用户UI端阻塞(转圈圈,无响应)等,造成体验较差。
如果你直接将这些耗时任务放入一个新的线程中,又会有新的问题,由于swing并不是线程安全的,多个线程进行修改UI组件可能会导致不可预测的效果。
swing线程模型
由于swing并不是线程安全的,开发swing程序时,应当遵从以下线程模型:
- 事件调度线程: 所有与用户界面相关的操作都必须在称为"事件调度线程"的单一线程中执行。
- 其他线程
Swing线程模型的基本原则是:所有用户界面的更新都必须在事件调度线程中进行。当用户与界面进行交互时,例如点击按钮或输入文本,Swing会生成相应的事件,并将其放入事件队列中。事件调度线程会从队列中依次取出事件,并在合适的时候执行相应的操作,例如更新界面、触发事件监听器等。
解决方案
开发者在编写Swing应用程序时,需要遵循以下几点:
- 任何涉及用户界面更新的操作都应该在事件调度线程中执行。可以使用Swing提供的工具类如SwingUtilities.invokeLater()或EventQueue.invokeLater()来将任务放入事件队列中。
- 长时间运行的任务应该在单独的线程中执行,以避免阻塞事件调度线程。可以使用SwingWorker类来实现这一点,它提供了方便的方法来在后台线程中执行任务,并在完成后更新用户界面。
使用 SwingUtilities.invokeLater() 的主要目的是确保界面更新操作在事件调度线程中执行,以避免多线程并发访问导致的线程安全问题。当需要在非事件调度线程(例如主线程或其他后台线程)中进行界面更新时,可以使用该方法来将任务切换到事件调度线程中执行。
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// 在事件调度线程中执行的任务
// 更新界面、触发事件监听器等操作
}
});
使用 SwingWorker 可以有效地处理耗时任务,并在任务执行过程中更新用户界面,提供更好的用户体验。
SwingWorker<Integer, String> worker = new SwingWorker<Integer, String>() {
@Override
protected Integer doInBackground() throws Exception {
// 在后台线程中执行的耗时任务
// 返回任务的结果
return 42;
}
@Override
protected void process(List<String> chunks) {
// 在事件调度线程中更新中间结果
// 可以用于更新进度条或显示其他中间状态
}
@Override
protected void done() {
try {
// 在任务完成后执行的操作
// 获取任务的结果并更新用户界面
int result = get();
// 更新界面等操作
} catch (InterruptedException | ExecutionException ex) {
// 处理异常情况
}
}
};
worker.execute(); // 启动 SwingWorker 执行任务