首次登录线程启动正常,再次登录线程无法启动问题求助
问题分析与解决方案
核心问题根源
从你的代码来看,线程第二次无法启动的主要原因是GPIO资源没有被正确释放,且重复初始化导致冲突,再加上静态类/变量的设计让状态无法重置:
- 第一次登录时,线程初始化了GPIO引脚、注册了中断监听,并且启动了一个死循环
for(;;);即使你调用this.setVisible(false)隐藏Login窗口,MainForm的线程依然在后台运行,GPIO资源也没有被释放。 - 第二次登录时,你又创建了一个新的
MainForm实例,再次执行GPIO初始化操作(比如GpioUtil.export()、GpioInterrupt.enablePinStateChangeCallback()),但这些引脚已经被之前的线程占用,会抛出异常导致线程启动失败。 - 另外,
pulses、display以及bca内部类都是static的,属于类级别而非实例级别,第二次登录时这些状态不会被重置,也会导致逻辑混乱。
分步解决方法
1. 重构MainForm:让线程和GPIO资源可管控
把静态内部类改为普通内部类,添加线程终止标志,实现资源释放逻辑:
import javax.swing.JLabel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; import com.pi4j.wiringpi.Gpio; import com.pi4j.wiringpi.GpioInterrupt; import com.pi4j.wiringpi.GpioInterruptListener; import com.pi4j.wiringpi.GpioInterruptEvent; import com.pi4j.wiringpi.GpioUtil; public class MainForm extends javax.swing.JFrame { // 把静态变量改为实例变量,每个MainForm实例独立维护 private volatile int pulses = 0; private boolean display = true; // 添加线程终止标志 private volatile boolean threadRunning = false; private Thread pulseThread; // 把静态内部类改为普通内部类 private class Bca implements Runnable{ @Override public void run() { threadRunning = true; // 创建监听(注意:这里每次创建新的监听,避免重复注册) GpioInterruptListener listener = new GpioInterruptListener() { @Override public void pinStateChange(GpioInterruptEvent event) { if (!threadRunning) return; // 线程停止后不再处理中断 if (event.getPin() == 0) { if (!event.getState()) { pulses++; display = true; } } if (event.getPin() == 7) { if (!event.getState()) { pulses += 10; display = true; } } } }; GpioInterrupt.addListener(listener); try { // 初始化WiringPi(检查是否已经初始化,避免重复调用) if (Gpio.wiringPiSetup() == -1) { System.out.println("WiringPi Setup Failed!"); return; } // 初始化引脚:先检查是否已导出,避免重复导出 int[] pins = {0,7}; for(int pin : pins){ if(!GpioUtil.isExported(pin)){ GpioUtil.export(pin, GpioUtil.DIRECTION_IN); GpioUtil.setEdgeDetection(pin, GpioUtil.EDGE_FALLING); Gpio.pinMode(pin, Gpio.INPUT); Gpio.pullUpDnControl(pin, Gpio.PUD_UP); GpioInterrupt.enablePinStateChangeCallback(pin); } } // 用threadRunning控制循环,代替死循环 while(threadRunning){ if (display) { // 注意:Swing组件更新要在EDT线程中执行 javax.swing.SwingUtilities.invokeLater(() -> { jTextField_pulse.setText(String.valueOf(pulses)); }); display = false; } try { Thread.sleep(100); // 加个小休眠,减少CPU占用 } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } finally { // 线程退出时释放资源 releaseGpioResources(); GpioInterrupt.removeListener(listener); threadRunning = false; } } } // 添加GPIO资源释放方法 private void releaseGpioResources(){ int[] pins = {0,7}; for(int pin : pins){ if(GpioUtil.isExported(pin)){ GpioInterrupt.disablePinStateChangeCallback(pin); GpioUtil.unexport(pin); } } } public MainForm() { initComponents(); // 给MainForm添加关闭事件,确保线程和资源被释放 this.addWindowListener(new java.awt.event.WindowAdapter() { @Override public void windowClosing(java.awt.event.WindowEvent evt) { stopPulseThread(); dispose(); // 彻底销毁窗口资源 } }); } // 启动线程的方法 public void startPulseThread(){ if(pulseThread == null || !pulseThread.isAlive()){ pulseThread = new Thread(new Bca()); pulseThread.start(); } } // 停止线程的方法 public void stopPulseThread(){ threadRunning = false; if(pulseThread != null && pulseThread.isAlive()){ pulseThread.interrupt(); try { pulseThread.join(1000); // 等待线程终止 } catch (InterruptedException e) { e.printStackTrace(); } } } @SuppressWarnings("unchecked") private void initComponents() { // 你的自动生成代码保持不变 } // 注意:MainForm的main方法可以保留,但实际登录时由Login创建实例 public static void main(String args[]) { java.awt.EventQueue.invokeLater(() -> { MainForm mf = new MainForm(); mf.setVisible(true); mf.startPulseThread(); }); } }
2. 修改Login的登录逻辑:确保资源正确切换
在Login中,要确保每次登录时,如果之前有MainForm实例存在,先停止它的线程并销毁,再创建新实例:
// 把Login中的jButton_loginActionPerformed方法修改如下: private void jButton_loginActionPerformed(java.awt.event.ActionEvent evt) { jLabel_asterisk1.setVisible(false); jLabel_asterisk2.setVisible(false); if(jTextField_username.getText().isEmpty()){ jLabel_asterisk1.setVisible(true); return; } String password = String.valueOf(jPasswordField.getPassword()); if(password.isEmpty()){ jLabel_asterisk2.setVisible(true); return; } Connection con = MyConnection.getConnection(); PreparedStatement ps; try { ps = con.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?"); ps.setString(1, jTextField_username.getText()); ps.setString(2, password); ResultSet rs = ps.executeQuery(); if(rs.next()){ // 先检查是否已有MainForm实例在运行(可以用静态变量保存实例) if(this.currentMainForm != null){ currentMainForm.stopPulseThread(); currentMainForm.dispose(); } // 创建新的MainForm实例 MainForm mf = new MainForm(); mf.setVisible(true); MainForm.jLabel_welcome.setText(jTextField_username.getText()); // 建议把jLabel_welcome改为实例变量 mf.startPulseThread(); // 调用MainForm的启动方法 this.currentMainForm = mf; // 保存实例引用,方便下次登录时销毁 this.setVisible(false); } else { JOptionPane.showMessageDialog(null, "Incorrect Username or Password!"); } } catch (SQLException ex) { Logger.getLogger(Login.class.getName()).log(Level.SEVERE, null, ex); } catch (IllegalArgumentException e){ JOptionPane.showMessageDialog(null, "You have already paid your latest balances, queue will proceed."); } } // 在Login类中添加一个实例变量保存MainForm引用 private MainForm currentMainForm = null;
3. 额外注意事项
- Swing线程安全:更新Swing组件(比如
jTextField_pulse.setText())必须在Event Dispatch Thread(EDT)中执行,所以用SwingUtilities.invokeLater()包裹,避免线程安全问题。 - 静态变量优化:把
jLabel_welcome从static改为实例变量,避免多个MainForm实例互相干扰。 - 异常捕获:可以在MainForm的线程run方法中添加try-catch,捕获GPIO相关的异常,方便调试。
这样修改后,每次登录都会创建新的MainForm实例,启动新的线程,并且在关闭MainForm或再次登录时,会正确释放GPIO资源、停止旧线程,解决第二次登录线程无法启动的问题。
内容的提问来源于stack exchange,提问作者patrickdg142
相关产品推荐
相关产品推荐

