我是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,允许其他线程读取它。
如果我错了,请纠正我。
答案 0 :(得分:2)
你是对的 - 我认为代码作者可能认为他们做了一些聪明的事情,通过在进入同步部分之前检查linkedSites数组是否为空来节省一些时间。这可能看起来很安全,因为在同步部分内再次检查了大小。
但是,Java内存模型并不能保证调用next()的线程会看到与最后一个线程处于相同状态的linkedSites进行修改,除非读取也是在同步部分完成的,所以理论上调用next的线程可能尽管另一个线程已将数据放入其中,但仍将数组视为空。每个线程都可能拥有自己的对象数据副本,这些副本只能通过同步代码块与其他线程的副本同步。因此,调用next的线程可能会错误地将数组视为空。
答案 1 :(得分:1)
严格意义上说,你是对的。但是,即使在多线程程序中,当您访问例如所有时,也不一定非同意。阅读我不知道整个程序,但next()
可能会被调用几次。如果线程错过了一些条目,可能其他一些将在以后捕获它。但是,正如你所说,并不能保证其他人会看到这些变化。
答案 2 :(得分:1)
linkedSites.size()应该在synchronized块内,否则可能看不到其他线程对linkedSites所做的更改。