You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的安全方式:

  1. 移除你自定义的publish方法
  2. 修改SwingWorker的泛型,指定要传递给EDT的数据类型
  3. 重写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,不会阻塞EDT
  • publish:将需要显示的数据(这里是数字j)发送到EDT的任务队列
  • process:在EDT中被调用,接收publish发送的数据,安全更新JTextArea等GUI组件,完全符合Swing的单线程规则

额外注意事项

如果后续需要更新label等其他GUI组件,同样要确保在EDT中执行:

  • 可以在process方法中一起处理
  • 或者使用SwingUtilities.invokeLater包装更新逻辑,比如:
SwingUtilities.invokeLater(() -> {
    label.setText("新状态");
});

内容的提问来源于stack exchange,提问作者Marllon Nasser

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 11:12:56