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

首次登录线程启动正常,再次登录线程无法启动问题求助

问题分析与解决方案

核心问题根源

从你的代码来看,线程第二次无法启动的主要原因是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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:22:40