一种易错的线程同步模型

下面有一段代码,是为了完成爬虫处理过程中,将已处理的 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();
                }
            }
            
        }
        
    }
}
上面的代码看起来没有问题,首先是将其加入到 Set 中,再查询 Set 是不是满了,若是,则将 Set 锁起来并再次查询,若真的满了,则删除前一半。


事实上,这段代码是有一个很严重的 Bug 的,当一个线程中 __urlDealedHashCode.add(url.hashCode()); 这句代码即将运行时,可能别的线程已经进入了下面的同步区,并且正在 remove 掉 Set 中的元素。此时会抛出 ConcurrentModificationException 异常。

问题出现的原因在于,__urlDealedHashCode.add(url.hashCode()); 这句没有加任何同步保护,尽管 __urlDealedHashCode 这个对象加锁了,但在没有syncronized的情况下,也是可以直接访问它的,所以在别的线程里面,对 Set 修改会发生那个异常。

最简单的改法就是对那个函数加锁。

你可能感兴趣的:(Java)