这个代码在关键部分是否违反互斥?

时间:2015-01-08 12:25:37

标签: java multithreading concurrency synchronized synchronized-block

我是Java新手并试图理解Java中的并发性。在探索时,我在Java并发上非常流行的page上看到了这段代码:

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;
}

保存在synchronized块之外,以防某些线程在add()或next()中的synchronized块内修改linkedSites,允许其他线程读取它。

如果我错了,请纠正我。

3 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

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