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

JSF PrimeFaces DataTable排序报ConcurrentModificationException异常

生产环境PrimeFaces DataTable排序触发ConcurrentModificationException故障说明

故障基本情况

  • 故障特征:生产环境偶发服务中断,异常触发后数秒内相同栈轨迹重复打印十余次,历史上未出现过同类问题
  • 触发时机:PrimeFaces执行DataTable排序操作阶段抛出异常,初步判断为表格绑定数据集被并发修改导致
  • 关联技术栈版本:
    • JSF 2.3.9
    • PrimeFaces 8.0
    • OmniFaces 3.11
    • Tomcat 8
  • 页面配置:故障页为发票开具功能页,对应托管Bean为javax.faces.view.ViewScoped作用域(非OmniFaces实现版本);页面包含客户信息录入、发票商品添加、支付信息填写三个核心模块,后两个模块通过facelets include引入,均配置了开启排序功能的DataTable,两个表格绑定的数据集会被其他模块的操作联动更新。

核心异常日志

05-Jul-2022 19:13:53.583 SEVERE [http-nio-8080-exec-746] com.sun.faces.application.view.FaceletViewHandlingStrategy.handleRenderException Error Rendering View[/pages/example.xhtml]
java.util.ConcurrentModificationException
    at java.util.ArrayList.sort(ArrayList.java:1466)
    at java.util.Collections.sort(Collections.java:177)
    at org.primefaces.component.datatable.feature.SortFeature.singleSort(SortFeature.java:167)
    at org.primefaces.component.datatable.DataTableRenderer.preRender(DataTableRenderer.java:141)
    at org.primefaces.component.datatable.DataTableRenderer.encodeEnd(DataTableRenderer.java:93)
    at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:949)
    at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1912)
    at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1908)
    at javax.faces.render.Renderer.encodeChildren(Renderer.java:176)
    at javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:918)
    at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1905)
    at com.sun.faces.context.PartialViewContextImpl$PhaseAwareVisitCallback.visit(PartialViewContextImpl.java:638)
    at com.sun.faces.component.visit.PartialVisitContext.invokeVisitCallback(PartialVisitContext.java:183)
    at javax.faces.component.UIForm.visitTree(UIForm.java:405)
    at javax.faces.component.UIComponent.visitTree(UIComponent.java:1747)
    at javax.faces.component.UIComponent.visitTree(UIComponent.java:1747)
    at com.sun.faces.context.PartialViewContextImpl.processComponents(PartialViewContextImpl.java:423)
    at com.sun.faces.context.PartialViewContextImpl.processPartial(PartialViewContextImpl.java:342)
    at org.primefaces.context.PrimePartialViewContext.processPartial(PrimePartialViewContext.java:65)
    at javax.faces.context.PartialViewContextWrapper.processPartial(PartialViewContextWrapper.java:252)
    at org.omnifaces.context.OmniPartialViewContext.processPartial(OmniPartialViewContext.java:122)
    at javax.faces.component.UIViewRoot.encodeChildren(UIViewRoot.java:1124)
    at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1905)
    at com.sun.faces.application.view.FaceletViewHandlingStrategy.renderView(FaceletViewHandlingStrategy.java:465)
    at com.sun.faces.application.view.MultiViewHandler.renderView(MultiViewHandler.java:194)
    at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:151)
    at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:151)
    at org.omnifaces.viewhandler.OmniViewHandler.renderView(OmniViewHandler.java:155)
    at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:126)
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:100)
    at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:223)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:671)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at ar.com.dux.jsf.filters.CacheFilter.doFilter(CacheFilter.java:27)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at ar.com.dux.jsf.filters.SecurityFilter.doFilter(SecurityFilter.java:84)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.primefaces.webapp.filter.FileUploadFilter.doFilter(FileUploadFilter.java:89)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.omnifaces.filter.GzipResponseFilter.doFilter(GzipResponseFilter.java:183)
    at org.omnifaces.filter.HttpFilter.doFilter(HttpFilter.java:108)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:196)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:698)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:364)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:624)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:831)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1650)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

根因分析

  • 从异常栈可直接定位抛出点为ArrayList.sort()方法:PrimeFaces 8.0的SortFeature.singleSort逻辑会直接调用Collections.sort()对DataTable绑定的原始List做原地排序,未做防御性拷贝。而ArrayList的排序实现会全程校验结构修改计数(modCount),如果排序期间有任何线程对List执行增、删、改操作导致计数变化,就会直接抛出ConcurrentModificationException。
  • 故障场景使用Mojarra JSF 2.3原生的javax.faces.view.ViewScoped实现,该作用域本身没有对托管Bean内存储的数据集做多线程并发访问防护:当用户在页面触发AJAX请求更新表格数据集时,如果此时存在另一个并发请求(比如快速点击排序、其他模块联动触发的局部刷新、PrimeFaces推送请求)刚好进入渲染响应阶段执行排序逻辑,两个线程会同时操作同一个ArrayList实例,直接触发并发修改异常。
  • 故障页面两个DataTable的数据集会被其他模块联动更新,进一步提升了并发冲突概率:当用户操作客户信息模块触发局部刷新时,后台业务逻辑可能正在修改商品、支付模块绑定的列表,此时如果商品/支付表格的渲染排序逻辑刚好执行,就会命中异常。异常触发后数秒内相同栈轨迹重复打印十余次,是因为PrimeFaces AJAX引擎在请求失败后触发了默认重试逻辑,重试时并发冲突状态还未结束,导致异常重复抛出。

修复方案

  • 所有对DataTable绑定的原始列表的增、删、改操作,统一加对象锁,锁使用托管Bean实例本身即可,保证数据修改和PrimeFaces排序操作不会并行执行。示例代码:
    // 所有修改商品列表的业务方法都加同步控制
    public synchronized void addInvoiceItem(InvoiceItem item) {
        invoiceItems.add(item);
    }
    
    public synchronized void removeInvoiceItem(InvoiceItem item) {
        invoiceItems.remove(item);
    }
    
    注意:如果使用Collections.synchronizedList包装列表,依然需要对排序这类复合操作加锁,无法靠线程安全集合本身避免异常。
  • 在DataTable的value属性对应的getter方法中,返回原始列表的防御性拷贝,让PrimeFaces的排序逻辑只操作拷贝出的临时对象,从根源上避免和其他线程的修改操作冲突。示例代码:
    public List<InvoiceItem> getInvoiceItems() {
        return new ArrayList<>(invoiceItems);
    }
    
    这种方式除了能彻底解决并发修改问题,还能避免PrimeFaces原地排序污染后台原始数据集顺序的问题,符合视图层逻辑不修改模型层数据的设计原则。对于发票开具这类单页列表数据量通常不超过百条的业务场景,拷贝带来的性能开销可以完全忽略。
  • 将原生ViewScoped替换为OmniFaces实现的org.omnifaces.cdi.ViewScoped,该实现对同一个视图实例的并发请求做了排队处理,能从作用域层面避免多线程同时操作托管Bean数据的问题,是JSF生产环境部署的通用最佳实践。
  • 调整页面AJAX请求配置:给联动更新、排序、提交类操作加上请求期间的控件禁用逻辑,避免用户快速点击触发重叠请求;同时关闭PrimeFaces AJAX的默认失败重试配置,避免异常发生后重复请求放大故障影响。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 15:36:23