2

我是 Java 新手,并试图了解 Java 中的并发性。在探索时,我在一个非常流行的 Java 并发页面上遇到了这段代码:

public class CrawledSites {
  private List<String> crawledSites = new ArrayList<String>();
  private List<String> linkedSites = new ArrayList<String>();

  public void add(String site) {
    synchronized (this) {
      if (!crawledSites.contains(site)) {
        linkedSites.add(site);
      }
    }
  }


/**
   * Get next site to crawl. Can return null (if nothing to crawl)
   */

  public String next() {
    if (linkedSites.size() == 0) {
      return null;
    }
    synchronized (this) {
      // Need to check again if size has changed
      if (linkedSites.size() > 0) {
        String s = linkedSites.get(0);
        linkedSites.remove(0);
        crawledSites.add(s);
        return s;
      }
      return null;
    }
  }

}

我认为这里的函数 next() 违反了互斥,如下所示:

if (linkedSites.size() == 0) {
  return null;
}

被保存在同步块之外,因此如果某个线程在 add() 或 next() 中修改同步块内的linkedSites,则允许其他线程读取它。

如果我错了,请纠正我。

4

3 回答 3

2

你是对的 - 我认为代码作者可能认为他们在做一些聪明的事情,通过在进入同步部分之前检查 linkedSites 数组是否为空来节省一点时间。这看起来很安全,因为在同步部分中再次检查了大小。

但是,Java 内存模型不保证调用 next() 的线程将看到与修改它的最后一个线程处于相同状态的linkedSites,除非读取也在同步部分中完成,因此理论上调用 next() 的线程可能会继续看到尽管另一个线程已将数据放入其中,但数组为空。每个线程都可能拥有自己的对象数据副本,该副本仅通过同步代码块与其他线程的副本同步。因此,调用 next 的线程可能会错误地将数组视为空。

于 2015-01-08T12:49:44.867 回答
1

严格意义上的互斥,你是对的。但即使在多线程程序中,当您访问例如阅读时,也不一定需要将所有内容同步。我不知道整个程序,但next()可能会被调用几次。如果线程错过了某个条目,则可能其他一些条目稍后会捕获它。但是,正如您所说,不能保证其他人会看到这些变化。

于 2015-01-08T12:43:25.513 回答
1

linkedSites.size() 应该在同步块内,否则它可能看不到其他线程对linkedSites 所做的更改。

于 2015-01-08T12:43:38.317 回答