JavaFX项目重载数据触发ConcurrentModificationException如何避免
异常根因
ConcurrentModificationException触发的核心原因是:你用来存储国家、客户等业务对象的集合(大概率是ArrayList、HashSet这类普通非线程安全集合),在被遍历执行填充TableView逻辑的同时,deleteData()方法又在对同一个集合做元素移除的结构修改操作,触发了Java集合的fail-fast校验机制,直接抛出异常。
同时你当前把JDBC数据库操作直接放在JavaFX UI线程(FXML的事件回调、initialize方法默认都运行在FX应用线程)执行,耗时的IO操作会阻塞UI渲染,容易出现界面卡顿无响应的问题。
解决方案
1. 用异步任务分离IO操作和UI操作
用JavaFX自带的Task组件把JDBC操作放到后台线程执行,所有集合修改、UI更新逻辑统一放到任务执行完成后的UI线程回调中,保证集合的修改和遍历不会同时发生:
private void reloadData() { // 后台任务仅处理数据库IO操作,不碰UI绑定的集合 Task<List<Customer>> reloadTask = new Task<>() { @Override protected List<Customer> call() throws SQLException { // 直接从数据库查询最新客户数据,其他业务数据同理查询 return Manager.queryAllCustomersFromDB(); } }; // 任务执行成功后切回UI线程更新数据 reloadTask.setOnSucceeded(e -> { List<Customer> latestCustomerData = reloadTask.getValue(); // 直接替换整个业务集合,不需要先删再插避免分步操作冲突 Manager.setCustomerList(latestCustomerData); // 再执行TableView填充逻辑,此时集合状态稳定,没有并发操作 customerTableView.setItems(FXCollections.observableList(latestCustomerData)); }); // 异常捕获逻辑 reloadTask.setOnFailed(e -> { reloadTask.getException().printStackTrace(); // 可自定义弹窗提示用户数据加载失败 }); // 启动后台线程执行任务 new Thread(reloadTask).start(); }
对应的Manager层可以废弃deleteData()+loadData()的分步操作逻辑,直接查询最新数据后替换全量集合,从根源避免分步操作带来的并发冲突。
2. 兼容原有逻辑的冲突规避方案
如果一定要保留现有deleteData()+loadData()的执行逻辑,可任选以下一种方案解决冲突:
- 把Manager层存储业务对象的集合替换为
CopyOnWriteArrayList这类线程安全集合,该集合遍历时会使用快照副本,集合修改不会触发并发修改异常 - 遍历填充TableView前,先对原集合做一次快照拷贝,遍历拷贝后的副本,原集合的修改不会影响遍历逻辑:
// 遍历前拷贝集合快照 List<Customer> customerSnapshot = new ArrayList<>(Manager.getCustomerList()); for (Customer customer : customerSnapshot) { // 执行TableView填充逻辑 }
3. 修复SQL注入风险
原有UPDATE语句采用字符串拼接的方式读取用户输入,存在严重的SQL注入风险,建议改用PreparedStatement替代普通Statement:
String updateSql = "UPDATE customers SET Customer_Name = ?, Address = ?, Postal_Code = ?, Phone = ? WHERE Customer_ID = ?"; PreparedStatement ps = DBConnection.getConnection().prepareStatement(updateSql); ps.setString(1, nameTextField.getText()); ps.setString(2, addressTextField.getText()); ps.setString(3, postalTextField.getText()); ps.setString(4, phoneTextField.getText()); ps.setString(5, customerTextField.getText()); ps.executeUpdate();
内容的提问来源于stack exchange,提问作者Tyler Quante
相关产品推荐
相关产品推荐

