JavaFX跨TableView设置项时触发ConcurrentModificationException问题排查
在JavaFX场景中,监听historyTableView.getSelectionModel().selectedItemProperty()获取选中行的Purchase对象,并将其数据设置到purchaseTableView时,触发了ConcurrentModificationException。该异常仅在选中historyTableView中除最后一行外的其他行时出现。
相关代码
public void refreshStock() { historyTableView.setItems(FXCollections.observableList(dao.getPurchaseList())); historyTableView.refresh(); } @Override @FXML public void initialize(URL location, ResourceBundle resources) { historyTableView.refresh(); purchaseTableView.refresh(); // 异常触发相关代码块 historyTableView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { log.debug("Clicked on: " + newValue.toString()); purchaseTableView.setItems(FXCollections.observableList(newValue.getSoldItemListStream())); historyTableView.getSelectionModel().clearSelection(); purchaseTableView.refresh(); }); }
异常栈信息
Exception in thread "JavaFX Application Thread" java.util.ConcurrentModificationException at java.base/java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1415) at java.base/java.util.ArrayList$SubList.size(ArrayList.java:1155) at ee.ut.math.tvt.salessystem.dataobjects.Purchase.toString(Purchase.java:66) at ee.ut.math.tvt.salessystem.ui.controllers.HistoryController.lambda$initialize$0(HistoryController.java:76) at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181) at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80) at javafx.base/javafx.beans.property.ReadOnlyObjectPropertyBase.fireValueChangedEvent(ReadOnlyObjectPropertyBase.java:80) at javafx.base/javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:102) at javafx.base/javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:113) at javafx.base/javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:147) at javafx.controls/javafx.scene.control.SelectionModel.setSelectedItem(SelectionModel.java:105) at javafx.controls/javafx.scene.control.MultipleSelectionModelBase.lambda$new$0(MultipleSelectionModelBase.java:67) at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136) at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80) ...
异常原因分析
核心触发点:从异常栈可见,
Purchase.toString()调用ArrayList$SubList.size()时触发了并发修改异常。这说明Purchase持有的soldItemList是某个原始ArrayList的子列表(SubList),而原始列表正在被修改(比如UI线程或后台线程更新数据源),子列表的修改检测机制因此抛出异常。非最后一行才触发的原因:
dao.getPurchaseList()返回的列表中,非最后一行的Purchase对象对应的子列表,其关联的原始列表可能在选中操作时正被refreshStock等方法修改(比如重新设置historyTableView的数据源,导致原始列表结构变更)。- 最后一行对应的子列表通常是原始列表的末尾片段,当原始列表被重新赋值时,这个子列表的修改计数器不会被触发,或者后续没有操作触发其修改检测逻辑,因此不会抛出异常。
监听器操作的叠加影响:监听器中调用
historyTableView.getSelectionModel().clearSelection()会触发选择模型的属性变更,间接导致historyTableView的数据源列表被遍历或修改,和当前正在执行的newValue.toString()(遍历子列表)形成并发修改,加剧了异常触发的概率。
解决方案
替换SubList为独立列表:修改
Purchase或DAO的实现,避免返回原始列表的子列表,而是创建新的独立列表:// 修改Purchase的getSoldItemListStream方法 public List<SoldItem> getSoldItemListStream() { return new ArrayList<>(this.soldItemList); // 将子列表转换为新的ArrayList }或者在
refreshStock中预处理列表,确保每个Purchase持有独立的商品列表:public void refreshStock() { List<Purchase> processedList = dao.getPurchaseList().stream() .map(purchase -> { Purchase newPurchase = new Purchase(); // 复制purchase的基础属性 newPurchase.setSoldItemList(new ArrayList<>(purchase.getSoldItemListStream())); return newPurchase; }) .collect(Collectors.toList()); historyTableView.setItems(FXCollections.observableList(processedList)); }延迟执行清除选择操作:用
Platform.runLater()包裹clearSelection(),避免在当前事件处理流程中触发列表修改:historyTableView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { log.debug("Clicked on: " + newValue.toString()); purchaseTableView.setItems(FXCollections.observableList(newValue.getSoldItemListStream())); // 延迟到当前事件循环结束后执行清除操作 Platform.runLater(() -> historyTableView.getSelectionModel().clearSelection()); purchaseTableView.refresh(); });设置不可变商品列表:将
Purchase中的商品列表设置为不可变,避免后续修改触发异常:public class Purchase { private final List<SoldItem> soldItemList; public Purchase(List<SoldItem> soldItems) { this.soldItemList = Collections.unmodifiableList(new ArrayList<>(soldItems)); } public List<SoldItem> getSoldItemListStream() { return soldItemList; } }
内容的提问来源于stack exchange,提问作者jeser_e

