Java并发实践。清单5.6

时间:2019-03-07 13:20:33

标签: java concurrency

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

我不明白的是-作者是说可以通过用ConcurrentModificationException包装HashSet来解决此代码。这将如何解决问题?即使对所有方法的访问都将由同一个内在锁进行同步和保护,但是一旦获得迭代器对象,就无法保证一旦进行迭代,另一个线程就不会修改集合。

这本书的语录:

  

如果HiddenIterator将HashSet封装为SynchronizedSet,并封装了同步,则不会发生这种错误。

Collections.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()

2 个答案:

答案 0 :(得分:3)

Collections.synchronizedSet()将集合包装在名为SynchronizedSet的内部类的实例中,扩展了SynchronizedCollection。现在让我们看一下SynchronizedCollection.toString()的实现方式:

public String toString() {
    synchronized (mutex) {return c.toString();}
}

基本上,迭代仍然存在,隐藏在c.toString()调用中,但是它已经与此包装器集合的所有其他方法同步。因此,您无需在代码中重复同步。

答案 1 :(得分:0)

已编辑

synchronizedSet():: toString()

正如Sergei Petunin正确指出的那样,toString()的{​​{1}}方法在内部负责同步,因此在这种情况下无需手动同步。

syncedSet()上的外部迭代

  

一旦获得了迭代器对象,就无法保证一旦进行迭代,另一个线程就不会修改集合。

在外部迭代的情况下,例如使用 for-each Collections.synchronizedSet(),将迭代封装在Iterator块中的方法是必需的/足够的。

这就是Collections.synchronizedSet()的JavaDoc指出

的原因
  

用户必须手动对返回的内容进行同步   遍历它或其任何synchronize(set)subSet或   headSet次浏览。

tailSet

手动同步

您的第二个版本具有类 SortedSet s = Collections.synchronizedSortedSet(new TreeSet()); ... synchronized (s) { Iterator i = s.iterator(); // Must be in the synchronized block while (i.hasNext()) foo(i.next()); } synchronized的{​​{1}}添加/删除方法也可以工作,但是由于添加/删除将被同步两次,因此引入了不必要的开销。 HiddenIteratorsynchronize(this)

但是,在这种情况下,您可以省略HiddenIterator,因为Collections.synchronizedSet(..)会处理访问专用Collections.synchronizedSet(..)字段时所需的所有同步。