如何实现线程安全的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
相关产品推荐
相关产品推荐

