Java 1.8中SwingWorker线程池添加Thread.sleep致GUI冻结的解决办法
问题分析与解决方案
你遇到的GUI冻结问题,核心原因是违反了Swing的单线程模型规则——你在后台线程中直接操作了Swing组件(JTextArea),加上Thread.sleep的延迟,导致线程安全冲突,最终让GUI失去响应。
问题根源
你自定义了publish方法,在doInBackground(后台线程)里直接调用它更新txtArea:
private void publish(final long num) { this.txtArea.append(num + "\n"); this.txtArea.setCaretPosition(this.txtArea.getDocument().getLength()); }
Swing要求所有GUI组件的创建、修改必须在**事件调度线程(EDT)**中执行,后台线程直接操作组件会引发线程安全问题,当加上Thread.sleep后,后台线程的操作延迟会放大这种冲突,导致GUI冻结。
解决方案
使用SwingWorker自带的publish+process机制,这是官方推荐的后台任务更新GUI的安全方式:
- 移除你自定义的
publish方法 - 修改SwingWorker的泛型,指定要传递给EDT的数据类型
- 重写
process方法,在EDT中安全更新GUI
修改后的WorkerDoSomething类代码如下:
import java.util.List; import java.util.Random; import javax.swing.JTextArea; import javax.swing.JLabel; import javax.swing.SwingWorker; public class WorkerDoSomething extends SwingWorker<Void, Long> { private JTextArea txtArea; private JLabel label; private Random r = new Random(); public WorkerDoSomething(final PanelFoo panelFooInstance) { this.txtArea = panelFooInstance.getTextArea(); this.label = panelFooInstance.getLabel(); } private Integer randomInt(final int min, final int max) { return this.r.nextInt((max - min) + 1) + min; } @Override protected Void doInBackground() throws Exception { final Integer randomNumber = randomInt(10000000, 1000000000); long j; int progress = 0; final int onePerCent = randomNumber / 100; final int onePerMillion = onePerCent / 10; for (j = 0; j <= randomNumber; j++) { if (j % onePerCent == 0) { progress = (int) j / onePerCent; setProgress(progress); } if (j % onePerMillion == 0) { // 调用SwingWorker自带的publish方法,将数据发送到EDT publish(j); } // 这里添加sleep模拟等待,不会再冻结GUI Thread.sleep(randomInt(1000, 5000)); } return null; } @Override protected void process(List<Long> chunks) { // 这个方法在EDT中执行,安全更新GUI组件 for (Long num : chunks) { this.txtArea.append(num + "\n"); this.txtArea.setCaretPosition(this.txtArea.getDocument().getLength()); } } }
为什么这样能解决问题?
doInBackground:在后台线程执行耗时的数据库轮询等操作,包括Thread.sleep,不会阻塞EDTpublish:将需要显示的数据(这里是数字j)发送到EDT的任务队列process:在EDT中被调用,接收publish发送的数据,安全更新JTextArea等GUI组件,完全符合Swing的单线程规则
额外注意事项
如果后续需要更新label等其他GUI组件,同样要确保在EDT中执行:
- 可以在
process方法中一起处理 - 或者使用
SwingUtilities.invokeLater包装更新逻辑,比如:
SwingUtilities.invokeLater(() -> { label.setText("新状态"); });
内容的提问来源于stack exchange,提问作者Marllon Nasser
相关产品推荐
相关产品推荐

