【问题标题】:Infinite Loop on Cached collection in Multithreaded environment多线程环境中缓存集合的无限循环
【发布时间】:2011-11-13 20:29:27
【问题描述】:

我的应用程序在带有 Spring 和 Hibernate 框架的 tomcat 上运行。它使用 EHCache 作为服务级别的缓存提供者。这意味着由服务类创建的对象被放入缓存中。 (不是休眠的 Dao 对象)。

这些缓存对象中有一些集合对象(HashSet、ArrayList、HashMap)。它们都不是同步集合。都不是线程安全的,但是放入缓存后不会被应用修改。

当我循环浏览这个集合时,我在很多场合都发现了无限循环。其中一些循环是迭代器循环,而另一些则是旧的基于 int 索引运行的 for 循环。

我设法通过用 Collections.synchronizedSet(new HashSet()) 替换 HashSet 来克服一个无限循环。但我不明白使用普通 HashSet 的真正问题,因为它从未被应用程序修改过。 (EHCache 会修改它们吗?)

如果这里使用非线程安全集合有任何问题,请向我解释。

public class HotelDetails implements Serializable { /*Objects in the cache */
private static final long serialVersionUID = 1L;
.....

private Set<String> facilities = new HashSet<String>();
}

以下循环无限运行并炸毁堆

if (hotelDetails.getFacilities() != null && hotelDetails.getFacilities().size() > 0) {
for (String fac : hotelDetails.getFacilities()) {
    TFacility f = of.createTFacility();
    f.setCode(fac);
    f.setValue(fac);
    facilities.getFacility().add(f);
}
}

更换HashSet,问题解决

public class HotelDetails implements Serializable { /*Objects in the cache */
private static final long serialVersionUID = 1L;
.....

private Set<String> facilities = Collections.synchronizedSet(new HashSet<String>());
}

这是另一个

private int getRatesStartIndex(GsRoomRate gsRoomRate, List<GsRate> gsRates, Date travelStart) {
    Integer startIndex = gsRoomRate.getGsRateIndexes().get(travelStart);
    if (startIndex==null) {
        for (startIndex=0; startIndex<gsRates.size(); startIndex++) {
            GsRate gsRate = gsRates.get(startIndex);
            if (travelStart.between(gsRate.getStartDate(), gsRate.getEndDate())) {
                gsRoomRate.getGsRateIndexes().put(travelStart, startIndex);
                break;
            }
        }
        if (startIndex>=gsRates.size()) startIndex = 0;
    }

    return startIndex;
}


public class GsRoomRate implements Serializable { /*Objects in the cache */
    private static final long serialVersionUID = 1L;
    private List<GsRate> gsRates = new ArrayList<GsRate>();
    private Map<Date, Integer> gsRateIndexes = new HashMap<Date, Integer>();
}

public class GsRate implements Serializable { /*Objects in the cache */

    private static final long serialVersionUID = 1L;

    private RBADate startDate;
    private RBADate endDate;
}

【问题讨论】:

  • 更改为同步集后,您的电话是否会返回?你怎么知道你进入了一个无限循环?您似乎表明您认为您处于无限循环中,因为您的堆空间不足...?

标签: java multithreading collections ehcache hashset


【解决方案1】:

EHCache 不会以任何方式修改您的对象。有一个例外:如果您有一个基于磁盘的缓存(即可以溢出到磁盘的缓存),那么 EHCache 将序列化您的对象,将它们写入磁盘并在需要时再次加载它们。

因此,如果序列化您的对象时出现问题并且您已将 EHCache 配置为溢出,这可能会导致问题,但感觉不像您的问题。

我的猜测是多个具有相同 ID 的对象被放入缓存中,或者对象在完全初始化之前被添加到缓存中。

如何调试这个?

  1. 如果有人在将集合添加到缓存后尝试修改集合,请使用 Collections.unmodifiable*() 获取错误。

  2. 保存集合的hashCode() 并验证它。 hashCode() 在以下情况下发生变化: a) 集合发生变化或 b) 如果集合中对象的 hashCode() 发生变化。

尤其是后者是不可预见问题的一个很好的来源:人们在 hashCode() 中使用非最终字段,将对象添加到集合/映射中,并且发生了奇怪的事情。

【讨论】:

  • 感谢 Aaron... 经过一番调查,发现问题是 HashMap 引起的。它是 get() 方法的内部循环无限运行。可以找到很多关于这个问题的信息。但不是直接的答案。如果不修改 HashMap,在多线程应用程序中使用 HashMap 是否安全?
  • 是的,只要不被修改,所有 Java 结构都是安全的。您的描述听起来像是线程 A 添加数据,而线程 B 从地图中获取数据。这可能会导致各种问题。试试 ConcurrentHashMap 看看是否能解决问题。或者 Collections.unmodifiable*() 按照我上面的回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-03
  • 1970-01-01
  • 2023-01-24
  • 1970-01-01
  • 2012-11-14
  • 1970-01-01
  • 2014-11-12
相关资源
最近更新 更多