下面有一段代码,是为了完成爬虫处理过程中,将已处理的 url 记录到数据库并用一个 Set 结构将其 hashCode 缓存到内存以快速判断一个 url 是不是已处理过。
public void queue(URLToDownload url)
{
__urlDealedHashCode.add(url.hashCode()); //记录此 url 已处理过
new UrlDealed(url).sync(); //将此已处理 url 记录到数据库
if(URLDEALED_MAX_SIZE < __urlDealedHashCode.size())
{
synchronized(__urlDealedHashCode)
{
if(URLDEALED_MAX_SIZE < __urlDealedHashCode.size()) //如果 __urlDealedHashCode 容纳的记录达到阈值则删除前一半
{
Iterator it = __urlDealedHashCode.iterator();
int halfMax = URLDEALED_MAX_SIZE >> 2;
while(halfMax < __urlDealedHashCode.size() && it.hasNext())
{
it.remove();
}
}
}
}
}
事实上,这段代码是有一个很严重的 Bug 的,当一个线程中 __urlDealedHashCode.add(url.hashCode()); 这句代码即将运行时,可能别的线程已经进入了下面的同步区,并且正在 remove 掉 Set 中的元素。此时会抛出 ConcurrentModificationException 异常。
问题出现的原因在于,__urlDealedHashCode.add(url.hashCode()); 这句没有加任何同步保护,尽管 __urlDealedHashCode 这个对象加锁了,但在没有syncronized的情况下,也是可以直接访问它的,所以在别的线程里面,对 Set 修改会发生那个异常。
最简单的改法就是对那个函数加锁。