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

如何实现线程安全的JSF EntityConverter?解决并发修改异常

解决EntityConverter的ConcurrentModificationException问题

嘿,我来帮你搞定这个并发异常的问题!咱们先拆解一下你遇到的核心问题:

你的EntityConverter用了WeakHashMap存储实体与UUID的映射,但哪怕换成Iterator遍历,还是抛出ConcurrentModificationException——根源在于只有getAsString加了同步锁,getAsObject的遍历操作完全没做线程同步。WeakHashMap本身不是线程安全的,它的迭代器是fail-fast机制:当遍历过程中有其他线程修改了map的结构(比如执行put操作),迭代器会立刻抛出这个异常。

方案一:给所有Map操作加上同步锁

最简单的修复方式,就是让getAsObject也在同一个锁对象(也就是entities)的同步块里执行遍历。这样所有对Map的读写操作都串行化,彻底避免并发冲突:

@FacesConverter(value = "entityConverter") 
public class EntityConverter implements Converter { 
    private static Map<Object, String> entities = new WeakHashMap<Object, String>(); 
    
    @Override 
    public String getAsString(FacesContext context, UIComponent component, Object entity) { 
        synchronized (entities) { 
            if (!entities.containsKey(entity)) { 
                String uuid = UUID.randomUUID().toString(); 
                entities.put(entity, uuid); 
                return uuid; 
            } else { 
                return entities.get(entity); 
            } 
        } 
    } 
    
    @Override 
    public Object getAsObject(FacesContext context, UIComponent component, String uuid) { 
        synchronized (entities) { // 给遍历操作加上同步锁
            Iterator<Map.Entry<Object, String>> tempIt = entities.entrySet().iterator(); 
            while (tempIt.hasNext()) { 
                Map.Entry<Object, String> nextObj = tempIt.next(); 
                if (nextObj.getValue().equals(uuid)) { 
                    return nextObj.getKey(); 
                } 
            } 
        }
        return null; 
    } 
} 

方案二:使用线程安全的Map包装类

如果你不想手动管理同步锁,也可以用Collections.synchronizedMap把WeakHashMap包装成线程安全版本。不过要注意:即使是同步包装的Map,遍历的时候仍然需要手动同步(因为迭代器本身不是同步的),否则还是可能触发并发异常:

@FacesConverter(value = "entityConverter") 
public class EntityConverter implements Converter { 
    // 用synchronizedMap包装WeakHashMap,实现基础线程安全
    private static Map<Object, String> entities = Collections.synchronizedMap(new WeakHashMap<Object, String>()); 
    
    @Override 
    public String getAsString(FacesContext context, UIComponent component, Object entity) { 
        if (!entities.containsKey(entity)) { 
            String uuid = UUID.randomUUID().toString(); 
            entities.put(entity, uuid); 
            return uuid; 
        } else { 
            return entities.get(entity); 
        } 
    } 
    
    @Override 
    public Object getAsObject(FacesContext context, UIComponent component, String uuid) { 
        synchronized (entities) { // 遍历必须手动同步
            for (Map.Entry<Object, String> entry : entities.entrySet()) { 
                if (entry.getValue().equals(uuid)) { 
                    return entry.getKey(); 
                } 
            } 
        }
        return null; 
    } 
} 

额外注意点

  • 确保你的实体对象正确实现了equals()和hashCode()方法,否则containsKey判断和遍历匹配可能会出现逻辑错误。
  • WeakHashMap的键是弱引用,实体对象被GC回收后,对应的条目会自动从Map中移除,这部分特性是正常的,但如果系统并发量极高,同步锁可能成为性能瓶颈,这时候可以考虑用更细粒度的锁,或者结合ConcurrentHashMap(但要注意ConcurrentHashMap没有弱引用版本,需要自己处理键的回收逻辑)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:12:19