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

启动1小时倒计时计时器时GUI冻结问题求助

解决Swing倒计时窗口冻结的问题

嘿,我太懂你遇到的这个糟心状况了!Swing的UI是靠**事件调度线程(EDT)**来驱动的——不管是按钮点击事件,还是更新Label的文本,全靠这一个线程忙活。你要是直接在EDT里调用Thread.sleep(1000),就相当于把这个唯一的“UI管家”给按住暂停了,窗口自然就僵住不动、没法响应任何操作了。

下面给你两种靠谱的解决办法,都是Swing开发里常用的:

方法一:用Swing Timer(官方推荐首选)

Swing专门提供了javax.swing.Timer类,它天生就是为UI定时任务设计的——它会在EDT里定时触发动作,完全不会阻塞UI线程。

举个完整的例子:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class CountdownTimer extends JFrame {
    private JLabel countdownLabel;
    private Timer timer;
    private int remainingSeconds = 3600; // 1小时=3600秒

    public CountdownTimer() {
        // 初始化窗口
        setTitle("1小时倒计时");
        setSize(300, 150);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new FlowLayout(FlowLayout.CENTER, 0, 30));

        // 创建倒计时标签
        countdownLabel = new JLabel(formatTime(remainingSeconds), SwingConstants.CENTER);
        countdownLabel.setFont(new Font("Arial", Font.BOLD, 24));
        add(countdownLabel);

        // 创建启动按钮
        JButton startBtn = new JButton("启动倒计时");
        startBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (timer == null) {
                    // 初始化Timer:间隔1000ms(1秒),每次触发时更新倒计时
                    timer = new Timer(1000, new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent evt) {
                            remainingSeconds--;
                            countdownLabel.setText(formatTime(remainingSeconds));
                            
                            // 倒计时结束时停止Timer
                            if (remainingSeconds <= 0) {
                                timer.stop();
                                countdownLabel.setText("时间到!");
                            }
                        }
                    });
                    timer.start(); // 启动Timer
                    startBtn.setEnabled(false); // 防止重复点击
                }
            }
        });
        add(startBtn);
    }

    // 把秒数格式化为HH:mm:ss
    private String formatTime(int seconds) {
        int hours = seconds / 3600;
        int minutes = (seconds % 3600) / 60;
        int secs = seconds % 60;
        return String.format("%02d:%02d:%02d", hours, minutes, secs);
    }

    public static void main(String[] args) {
        // 确保UI在EDT里初始化
        SwingUtilities.invokeLater(() -> {
            new CountdownTimer().setVisible(true);
        });
    }
}

方法二:后台线程+EDT更新UI

如果你非要用Thread来实现,那必须把Thread.sleep()放到后台线程里,绝对不能在EDT里调用。而且更新UI的时候,必须通过SwingUtilities.invokeLater()把更新操作抛回EDT执行,不然会有线程安全问题。

示例代码:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class CountdownThread extends JFrame {
    private JLabel countdownLabel;
    private JButton startBtn;
    private int remainingSeconds = 3600;
    private Thread countdownThread;
    private boolean isRunning = false;

    public CountdownThread() {
        setTitle("1小时倒计时");
        setSize(300, 150);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new FlowLayout(FlowLayout.CENTER, 0, 30));

        countdownLabel = new JLabel(formatTime(remainingSeconds), SwingConstants.CENTER);
        countdownLabel.setFont(new Font("Arial", Font.BOLD, 24));
        add(countdownLabel);

        startBtn = new JButton("启动倒计时");
        startBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (!isRunning) {
                    isRunning = true;
                    startBtn.setEnabled(false);
                    
                    // 启动后台线程
                    countdownThread = new Thread(() -> {
                        while (remainingSeconds > 0 && isRunning) {
                            try {
                                Thread.sleep(1000); // 这里sleep在后台线程,不会阻塞EDT
                                remainingSeconds--;
                                
                                // 必须回到EDT更新UI
                                SwingUtilities.invokeLater(() -> {
                                    countdownLabel.setText(formatTime(remainingSeconds));
                                });
                            } catch (InterruptedException ex) {
                                Thread.currentThread().interrupt();
                                break;
                            }
                        }
                        
                        // 倒计时结束后更新UI
                        SwingUtilities.invokeLater(() -> {
                            if (remainingSeconds <= 0) {
                                countdownLabel.setText("时间到!");
                            }
                            isRunning = false;
                            startBtn.setEnabled(true);
                        });
                    });
                    countdownThread.start();
                }
            }
        });
        add(startBtn);
    }

    private String formatTime(int seconds) {
        int hours = seconds / 3600;
        int minutes = (seconds % 3600) / 60;
        int secs = seconds % 60;
        return String.format("%02d:%02d:%02d", hours, minutes, secs);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            new CountdownThread().setVisible(true);
        });
    }
}

简单总结下:

  • 优先用Swing Timer,它是为SwingUI量身定做的,代码更简洁,也不用操心线程安全问题
  • 如果用后台线程,一定要记住:UI更新必须在EDT里做,别直接在后台线程碰Label、Button这些组件

内容的提问来源于stack exchange,提问作者Stalo Michael

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 04:18:21