在《 Java Concurrency in Practice》中,作者给出了一个非线程安全类的以下示例,该类在后台调用set对象上的迭代器,如果涉及多个线程,则可能会导致ConcurrentModificationException。可以理解,一个线程正在修改集合,另一个线程对其进行迭代,并且-繁荣!

我不明白的是-作者是说可以通过将HashSetCollections.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);
        }
    }
}

或者,作为替代方案,可以保留synchronizedadd()方法的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()调用中,但是它已经与此包装器集合的所有其他方法同步。因此,您无需在代码中重复同步。

09-04 20:02