Java Timer.scheduleAtFixedRate运行时无法修改周期求助
问题根源分析
你遇到的问题很典型:Timer.scheduleAtFixedRate() 调用时会一次性读取当时的intperiod值并固定下来,后续修改intperiod变量根本不会影响已经在运行的Timer任务周期——就像你定了一个闹钟,之后改了设定的间隔,闹钟不会自动跟着变,得重新设置才行。
另外还有个小隐患:你在后台TimerTask里直接调用repain()(应该是repaint()?你代码里打错了)和更新Swing组件(比如jLabel6.setText()),这是不安全的,因为Swing组件必须在**事件调度线程(EDT)**里操作,后台线程直接修改可能导致界面卡顿或异常。
推荐解决方案:改用Swing Timer
对于Swing应用,官方更推荐使用javax.swing.Timer,它天生适配EDT,而且支持动态修改延迟时间,完美解决你的滑块调节周期需求。下面是修改后的关键代码:
首先,替换Timer类型并初始化:
import javax.swing.Timer; // 注意导入这个Timer,不是java.util.Timer public class GameOfLife extends javax.swing.JFrame { int intperiod = 100; int wid = 50, hei = 50; boolean[][] currentMove = new boolean[hei][wid], nextMove = new boolean[hei][wid]; boolean play; public static int intpopulation = 0; Image offScrImg; Graphics offScrGraph; public static int intGeneration = 0; Timer swingTimer; // 换成Swing Timer public GameOfLife() { initComponents(); offScrImg = createImage(jPanel2.getWidth(), jPanel2.getHeight()); offScrGraph = offScrImg.getGraphics(); // 初始化Swing Timer swingTimer = new Timer(intperiod, e -> { if (play) { intGeneration++; // 确保在EDT线程更新界面组件 SwingUtilities.invokeLater(() -> { jLabel6.setText("Generation: " + intGeneration); }); // 计算下一代状态 for (int i = 0; i < hei; i++) { for (int j = 0; j < wid; j++) { nextMove[i][j] = decide(i, j); } } // 同步当前状态 System.arraycopy(nextMove, 0, currentMove, 0, hei); // 在EDT线程重绘界面 SwingUtilities.invokeLater(this::repain); } }); swingTimer.start(); // 启动Timer repain(); }
然后修改滑块的事件处理方法,直接更新Timer延迟:
private void jSlider1StateChanged(javax.swing.event.ChangeEvent evt) { int newPeriod = jSlider1.getValue(); jLabel4.setText(newPeriod + " milliseconds"); // 修正拼写错误:milisecounds → milliseconds intperiod = newPeriod; // 直接修改Swing Timer的延迟,立即生效 swingTimer.setDelay(newPeriod); }
如果你坚持用java.util.Timer的解决方案
如果你不想换Timer类型,那每次滑块变化时,需要取消原来的TimerTask,重新创建并调度新任务:
首先,把TimerTask设为成员变量方便管理:
public class GameOfLife extends javax.swing.JFrame { // ... 其他变量 Timer time = new Timer(); TimerTask currentTask; // 新增成员变量存储当前任务
然后抽离Timer任务的创建逻辑:
public GameOfLife() { initComponents(); // ... 其他初始化代码 startOrUpdateTimer(); // 调用初始化方法 repain(); } // 新增方法:启动或更新Timer任务 private void startOrUpdateTimer() { // 如果已有任务,先取消 if (currentTask != null) { currentTask.cancel(); } // 创建新任务 currentTask = new TimerTask() { public void run() { if (play == true) { intGeneration++; // 必须用SwingUtilities.invokeLater更新Swing组件 SwingUtilities.invokeLater(() -> { jLabel6.setText("Generation: " + intGeneration); }); // 计算下一代状态 for (int i = 0; i < hei; i++) { for (int j = 0; j < wid; j++) { nextMove[i][j] = decide(i, j); } } System.arraycopy(nextMove, 0, currentMove, 0, hei); // 在EDT线程重绘 SwingUtilities.invokeLater(() -> repain()); } } }; // 重新调度任务 time.scheduleAtFixedRate(currentTask, 0, intperiod); }
最后修改滑块事件触发更新:
private void jSlider1StateChanged(javax.swing.event.ChangeEvent evt) { int newPeriod = jSlider1.getValue(); jLabel4.setText(newPeriod + " milliseconds"); intperiod = newPeriod; // 更新Timer任务 startOrUpdateTimer(); }
额外小提示
- 你代码里的
repain()是拼写错误,要改成repaint()。 decide()方法里更新jLabel5.setText()的逻辑也要放到SwingUtilities.invokeLater()里,避免线程安全问题。
内容的提问来源于stack exchange,提问作者David Sadelski
相关产品推荐
相关产品推荐

