【问题标题】:Java and synchronized linked list [duplicate]Java和同步链表[重复]
【发布时间】:2014-12-14 22:46:43
【问题描述】:

我有问题。第一个是为什么当我们运行这个函数时会出现 ConcurrentModificationException ?

public static void testList() {
    List<String> list = new ArrayList<String>();
    list.add("str3");

    for (String st : list) {
        if (st.equalsIgnoreCase("str3")) {
            list.remove("str3");
        }
    }
    System.out.println(list);
}

我的事情,因为增强了使用迭代器(检查modificationsCount),但我要求确定。这就是异常的原因吗?

第二个问题是如果我使用Collections.synchronizedList(new LinkedList&lt;Something&gt;()); 我可以使用 2 个或更多增强的 for 循环吗?例如,我必须线程,有时第一个从集合中删除元素,有时第二个在集合中添加元素。我认为即使我们使用迭代器也应该是线程保存(我认为使用迭代器进行了增强)。
提前致谢。

【问题讨论】:

    标签: java multithreading collections synchronization


    【解决方案1】:

    如果您在迭代列表时从列表中删除元素,您将获得ConcurrentModificationException,就像您在上面的示例中所做的那样。为避免这种情况,您应该使用显式迭代器而不是增强的 for 循环,并在找到必须删除的元素时在迭代器而不是列表上调用 remove()

    for (Iterator<String> i = list.iterator(); i.hasNext(); ) {
        String st = i.next();
        if (st.equalsIgnoreCase("str3")) {
            // Remove the element that the iterator is currently pointing to
            i.remove();
        }
    }
    

    仅使用Collections.synchronizedList(...) 包装列表并不能让您可以让多个线程同时遍历列表,其中一个线程正在从列表中删除元素。当你这样做时,你仍然会得到一个ConcurrentModificationException。使用Collections.synchronizedList(...) 包裹列表将使列表中的各个方法同步,但多个方法之间不会同步。

    您必须通过正确同步您自己的迭代和删除元素的方法,确保如果一个线程正在从列表中删除元素,则没有其他线程正在迭代列表。

    【讨论】:

    • 感谢您的回答。因此,如果我有两个线程并且每个线程中都有一个迭代器(并且其中至少一个更改了集合),我必须同步迭代(因为如果一个进行更改,另一个迭代器将检查 modCount 和 Boom)?还有一个问题 - 如何实现引发异常的增强,但普通迭代器不会。提前致谢。
    • 是的,如果至少有一个线程正在更改集合,则需要在整个循环中进行同步。第二个问题:诀窍是您通过在迭代器上而不是在列表本身上调用 remove() 来删除 - 这样迭代器就知道集合已更改。使用增强的for-loop,您无法访问迭代器,因此您无法在迭代器上调用remove()
    【解决方案2】:

    对于第一个问题,是的。如果正在枚举列表,则在修改列表时是常见的例外。

    第二个问题,是的。但是您需要为每个 for 循环使用 synchronized

    【讨论】:

    • 感谢您的回答。
    【解决方案3】:

    迭代器未在 SynchronizedCollection 中同步。我附上了 SynchronizedCollection 代码。

    static class SynchronizedCollection<E> implements Collection<E>, Serializable {
            private static final long serialVersionUID = 3053995032091335093L;
    
            final Collection<E> c;  // Backing Collection
            final Object mutex;     // Object on which to synchronize
    
            SynchronizedCollection(Collection<E> c) {
                this.c = Objects.requireNonNull(c);
                mutex = this;
            }
    
            SynchronizedCollection(Collection<E> c, Object mutex) {
                this.c = Objects.requireNonNull(c);
                this.mutex = Objects.requireNonNull(mutex);
            }
    
            public int size() {
                synchronized (mutex) {return c.size();}
            }
            public boolean isEmpty() {
                synchronized (mutex) {return c.isEmpty();}
            }
            public boolean contains(Object o) {
                synchronized (mutex) {return c.contains(o);}
            }
            public Object[] toArray() {
                synchronized (mutex) {return c.toArray();}
            }
            public <T> T[] toArray(T[] a) {
                synchronized (mutex) {return c.toArray(a);}
            }
    
            public Iterator<E> iterator() {
                return c.iterator(); // Must be manually synched by user!
            }
    
            public boolean add(E e) {
                synchronized (mutex) {return c.add(e);}
            }
            public boolean remove(Object o) {
                synchronized (mutex) {return c.remove(o);}
            }
    
            public boolean containsAll(Collection<?> coll) {
                synchronized (mutex) {return c.containsAll(coll);}
            }
            public boolean addAll(Collection<? extends E> coll) {
                synchronized (mutex) {return c.addAll(coll);}
            }
            public boolean removeAll(Collection<?> coll) {
                synchronized (mutex) {return c.removeAll(coll);}
            }
            public boolean retainAll(Collection<?> coll) {
                synchronized (mutex) {return c.retainAll(coll);}
            }
            public void clear() {
                synchronized (mutex) {c.clear();}
            }
            public String toString() {
                synchronized (mutex) {return c.toString();}
            }
            // Override default methods in Collection
            @Override
            public void forEach(Consumer<? super E> consumer) {
                synchronized (mutex) {c.forEach(consumer);}
            }
            @Override
            public boolean removeIf(Predicate<? super E> filter) {
                synchronized (mutex) {return c.removeIf(filter);}
            }
            @Override
            public Spliterator<E> spliterator() {
                return c.spliterator(); // Must be manually synched by user!
            }
            @Override
            public Stream<E> stream() {
                return c.stream(); // Must be manually synched by user!
            }
            @Override
            public Stream<E> parallelStream() {
                return c.parallelStream(); // Must be manually synched by user!
            }
            private void writeObject(ObjectOutputStream s) throws IOException {
                synchronized (mutex) {s.defaultWriteObject();}
            }
        }
    

    根据文档 Iterator is SynchronizedCollection is not synchronized 。因此,如果迭代一个线程,另一个线程可以添加/删除。所以ConcurrentModificationException就会来。

    【讨论】:

    • 这或多或少与Collections.synchronizedList(...) 所做的相同,并且不会在跨方法调用时保护您免受ConcurrentModificationException 的影响。
    • @Jesper 这不是或多或少。这只是 synchronizedList 代码
    • 感谢您的回答!
    • @Jesper 对你有帮助吗
    • 那么,如果我在某个地方迭代集合,那么使用同步集合的原因是什么?我的意思是我必须通过集合来同步这个迭代(例如),但是添加、删除和其他操作是由其他东西同步的。因此,如果我手动同步所有这些(因为要由同一个对象同步),我应该只在我没有 Iteration 的地方使用这个同步集合(java 附带)吗?提前致谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-20
    • 2016-05-08
    • 2021-12-12
    • 1970-01-01
    • 1970-01-01
    • 2014-02-07
    • 2012-03-02
    相关资源
    最近更新 更多