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

Telegram单词学习Bot遭遇ConcurrentModificationException问题求助

问题分析与解决方案

核心原因定位

java.util.ConcurrentModificationException 本质是集合在遍历过程中被并发修改,结合你Telegram Bot的场景(首次添加正常、再次触发/start报错,异常来自线程池线程pool-1-thread-1),大概率是两类问题:

  1. SQLite连接管理类存在线程不安全的共享集合/状态,被Bot多线程请求同时操作;
  2. Bot主逻辑中存在未做线程安全保护的共享数据结构,在Dispatcher线程池处理请求时引发冲突。

针对性排查与修复

1. 检查SQLite连接管理的线程安全性

SQLite是文件型数据库,默认连接不支持多线程复用,若你的连接管理类存在以下情况,直接触发异常:

  • 多个线程共享同一个Connection实例;
  • 用非线程安全集合(比如ArrayList)管理连接池,且未加锁保护遍历/修改操作;
  • 关闭连接时未正确从集合中移除,导致遍历连接池时出现无效条目。

修复建议:

  • 彻底放弃复用Connection实例,每个请求单独创建并关闭(确认你的实现真的是每次新建,而非复用了某个共享连接);
  • 若用连接池,必须使用线程安全集合(比如CopyOnWriteArrayList),或对集合的所有操作加synchronized锁;
  • 开启SQLite的WAL模式提升并发安全性:在连接URL后追加?journal_mode=WAL。

2. 检查Bot主逻辑的线程安全

Telegram Bot的Dispatcher通过线程池处理请求,若Bot类中存在共享的非线程安全对象(比如临时存储用户输入的List/Map),两次/start请求并发触发时会引发遍历修改冲突。

修复建议:

  • 所有共享数据结构替换为线程安全实现:用ConcurrentHashMap替代HashMap,CopyOnWriteArrayList替代ArrayList;
  • 对共享对象的读写操作加锁:如果必须用非线程安全集合,在遍历、添加、删除操作外包裹synchronized块;
  • 避免在Bot成员变量中存储用户临时状态,改用Telegram Bot API提供的userData或直接存入数据库。

3. 结合异常堆栈精准定位

从异常堆栈中找到最顶部的业务代码栈帧:

  • 如果堆栈指向连接管理类的集合遍历方法,直接针对连接池的线程安全改造;
  • 如果指向Bot命令处理中的集合操作,就修复业务逻辑的线程安全问题。

代码示例参考

线程安全的SQLite连接管理(简易版)

public class SQLiteConnectionManager {
    private static final String DB_URL = "jdbc:sqlite:words.db?journal_mode=WAL";
    private static final CopyOnWriteArrayList<Connection> connectionPool = new CopyOnWriteArrayList<>();
    private static final int MAX_POOL_SIZE = 5;

    public static Connection getConnection() throws SQLException {
        // 遍历可用连接(线程安全集合支持并发遍历)
        for (Connection conn : connectionPool) {
            if (!conn.isClosed()) {
                return conn;
            }
        }
        // 无可用则创建新连接
        if (connectionPool.size() < MAX_POOL_SIZE) {
            Connection newConn = DriverManager.getConnection(DB_URL);
            connectionPool.add(newConn);
            return newConn;
        }
        throw new SQLException("连接池已满");
    }

    public static void closeConnection(Connection conn) {
        if (conn != null) {
            try {
                conn.close();
                connectionPool.remove(conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

Bot命令处理的线程安全改造

public class WordBot extends TelegramLongPollingBot {
    // 用线程安全集合存储用户临时输入状态
    private final ConcurrentHashMap<Long, String> userPendingOps = new ConcurrentHashMap<>();

    @Override
    public void onUpdateReceived(Update update) {
        if (update.hasMessage() && update.getMessage().hasText()) {
            String text = update.getMessage().getText();
            long chatId = update.getMessage().getChatId();
            
            if (text.equals("/start")) {
                userPendingOps.put(chatId, "waiting_for_word");
                sendMessage(chatId, "请输入要添加的单词:");
            } else if ("waiting_for_word".equals(userPendingOps.get(chatId))) {
                // 存储单词到数据库,用try-with-resources自动关闭连接
                try (Connection conn = SQLiteConnectionManager.getConnection()) {
                    String sql = "INSERT INTO words (word, chat_id) VALUES (?, ?)";
                    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
                        pstmt.setString(1, text);
                        pstmt.setLong(2, chatId);
                        pstmt.executeUpdate();
                        sendMessage(chatId, "单词添加成功!");
                        userPendingOps.remove(chatId);
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                    sendMessage(chatId, "添加失败,请重试");
                }
            }
        }
    }

    // 封装发送消息方法
    private void sendMessage(long chatId, String text) {
        SendMessage message = new SendMessage();
        message.setChatId(chatId);
        message.setText(text);
        try {
            execute(message);
        } catch (TelegramApiException e) {
            e.printStackTrace();
        }
    }

    // 实现Bot的其他必填方法...
}

额外注意事项

  • 不要在Bot成员变量中持有Connection实例,必须每次使用都从连接池获取或新建;
  • 控制Dispatcher线程池的线程数量,SQLite对并发写入的支持有限,建议线程数不超过5;
  • 若使用ORM框架操作数据库,确保框架本身做了线程安全处理。

内容的提问来源于stack exchange,提问作者Ivan Ivanov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 08:00:27