解决java.util.ConcurrentModificationException

有时候会遇到某些情况,我们需要一个存储不重复的容器。这时候我们会想到set,那么在多线程并发操作的情况下,我们就需要做同步。
Set怎么出现同步集合呢?
如果要同步非同步的集合

Collection c=Collections.synchronizedCollection(new ArrayList());
List list=Collections.synchronizedList(new ArrayList());
Set s=Collections.synchronizedSet(new HashSet());
Map m=Collections.synchronizedMap(new HashMap());

上网找到这几种方法,可以同步原本非同步的集合。但是这样就能确保没有问题么,我们运行下边的代码:

/**
 * Created by STVEN0KING
 * Date: 2016/10/31.
 * Description:
 * 该异常表示迭代器迭代过程中,迭代的对象发生了改变,如数据项增加或删除。
 * [解决方案]:由于迭代对象不是线程安全,在迭代的过程中,会检查modCount是否和初始modCount即expectedModCount一致,
 *            如果不一致,则认为数据有变化,迭代终止并抛出异常。
 *            常出现的场景是,两个线程同时对集合进行操作,线程1对集合进行遍历,
 *            而线程2对集合进行增加、删除操作,此时将会发生ConcurrentModificationException异常。
 * 解决方法:多线程访问时要增加同步锁,或者建议使用线程安全的集合:
 *          如ConcurrentHashMap替换HashMap,CopyOnWriteArrayList替换ArrayList。
 */
public class HashSetTest {
    public static void main(String[] args) {
        Set<Long> s = Collections.synchronizedSet(new HashSet<Long>());

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100000; i++) {
                    //synchronized (s) {
                        s.add((long) i);
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1);
                    //synchronized (s) {
                        for (Long l : s) {
                            System.out.println(l);
                        }
                    //}
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }).start();
    }
}

结果会抛出异常:

java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
    at java.util.HashMap$KeyIterator.next(HashMap.java:1461)
    at com.tzx.HashSetTest$2.run(HashSetTest.java:35)
    at java.lang.Thread.run(Thread.java:745)

为什么呢,我们来看看源码

//Collections.java部分代码省略
public static <T> Set<T> synchronizedSet(Set<T> s) {
    return new SynchronizedSet<>(s);
}

static class SynchronizedSet<E>
      extends SynchronizedCollection<E>
      implements Set<E> {
    private static final long serialVersionUID = 487447009682186044L;

    SynchronizedSet(Set<E> s) {
        super(s);
    }
    SynchronizedSet(Set<E> s, Object mutex) {
        super(s, mutex);
    }

    public boolean equals(Object o) {
        if (this == o)
            return true;
        synchronized (mutex) {return c.equals(o);}
    }
    public int hashCode() {
        synchronized (mutex) {return c.hashCode();}
    }
}

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 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();}
    }
}

我们重点看SynchronizedCollection类的add和iterator方法,add方法自己就做了同步,而iterator(// Must be manually synched by user!)。

所以是做并发读写的时候我们需要自己做同步(PS:打开上面代码中的注释)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值