在《 Java Concurrency in Practice》中,作者给出了一个非线程安全类的以下示例,该类在后台调用set
对象上的迭代器,如果涉及多个线程,则可能会导致ConcurrentModificationException
。可以理解,一个线程正在修改集合,另一个线程对其进行迭代,并且-繁荣!
我不明白的是-作者是说可以通过将HashSet
与Collections.synchronizedSet()
包装在一起来解决此代码。这将如何解决问题?即使对所有方法的访问将由相同的固有锁进行同步并加以保护,但是一旦获得迭代器对象,就无法保证一旦进行迭代,另一个线程就不会修改集合。
从书中引用:
如果HiddenIterator将HashSet封装为SynchronizedSet,并封装了同步,则不会发生这种错误。
public class HiddenIterator {
//Solution :
//If HiddenIterator wrapped the HashSet with a synchronizedSet, encapsulating the synchronization,
//this sort of error would not occur.
//@GuardedBy("this")
private final Set<Integer> set = new HashSet<Integer>();
public synchronized void add(Integer i) {
set.add(i);
}
public synchronized void remove(Integer i) {
set.remove(i);
}
public void addTenThings() {
Random r = new Random();
for (int i = 0; i < 10; i++)
add(r.nextInt());
/*The string concatenation gets turned by the compiler into a call to StringBuilder.append(Object),
* which in turn invokes the collection's toString method - and the implementation of toString in
* the standard collections iterates the collection and calls toString on each element to
* produce a nicely formatted representation of the collection's contents. */
System.out.println("DEBUG: added ten elements to " + set);
}
}
如果有人可以帮助我理解这一点,我将不胜感激。
我认为这是可以解决的:
public class HiddenIterator {
private final Set<Integer> set = Collections.synchronizedSet(new HashSet<Integer>());
public void add(Integer i) {
set.add(i);
}
public void remove(Integer i) {
set.remove(i);
}
public void addTenThings() {
Random r = new Random();
for (int i = 0; i < 10; i++)
add(r.nextInt());
// synchronizing in set's intrinsic lock
synchronized(set) {
System.out.println("DEBUG: added ten elements to " + set);
}
}
}
或者,作为替代方案,可以保留
synchronized
和add()
方法的remove()
关键字。在这种情况下,我们将在this
上进行同步。另外,我们必须在this
中添加一个同步块(再次在addTenThings()
上再次同步),该块将包含一个操作-使用隐式迭代进行记录:public class HiddenIterator {
private final Set<Integer> set = new HashSet<Integer>();
public synchronized void add(Integer i) {
set.add(i);
}
public synchronized void remove(Integer i) {
set.remove(i);
}
public void addTenThings() {
Random r = new Random();
for (int i = 0; i < 10; i++)
add(r.nextInt());
synchronized(this) {
System.out.println("DEBUG: added ten elements to " + set);
}
}
}
最佳答案
Collections.synchronizedSet()
将集合包装在名为SynchronizedSet
的内部类的实例中,从而扩展了SynchronizedCollection
。现在让我们看看如何实现SynchronizedCollection.toString()
:
public String toString() {
synchronized (mutex) {return c.toString();}
}
基本上,迭代仍然存在,隐藏在
c.toString()
调用中,但是它已经与此包装器集合的所有其他方法同步。因此,您无需在代码中重复同步。