Java多线程与并发_集合类之并发修改异常

Java多线程与并发_集合类之并发修改异常

总有一段难熬的日子,让你自我怀疑。不过当你再经历多一点,会发现,那只是生活的常态

在平时多线程操作集合类的时候,往往会遇到一些问题

一、CopyOnWriteArrayList

1.普通java集合类

List list = new ArrayList<>();
    for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }

结果抛出ConcurrentModificationException并发修改异常

Exception in thread "8" Exception in thread "20" Exception in thread "23" java.util.ConcurrentModificationException
[4cef5ee1, e77e63c5, b18fe499, 9a26690e, d0e109f2, f698862c, 453a9e46]
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
[4cef5ee1, e77e63c5, b18fe499, 9a26690e, d0e109f2, f698862c, 453a9e46, 32ef5223, 93584cff, 56a86b36]
...

2.导致原因

并发争抢修改导致,参考我们的花名册签名情况
一个人正在写入,另外一个人过来抢,导致数据不一致异常。并发修改异常

3.解决方案

3.1 new Vector<>()
3.2 Collections.synchronizedList(new ArrayList<>())
*3.3 CopyOnWriteArrayList<>() 写时复制
//   List list = new Vector<>()();
//   List list = Collections.synchronizedList(new ArrayList<>());
    List list = new CopyOnWriteArrayList<>();
    for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }

结果

[e5c5137e, 6970878c, daec20d2]
[e5c5137e, 6970878c, daec20d2, be6f8c58, 034daf30, c3aa7d98, 588f9f91, a2e862c5]
[e5c5137e, 6970878c, daec20d2, be6f8c58, 034daf30, c3aa7d98, 588f9f91]
...
Process finished with exit code 0

写时复制

CopyOnWrite容器即写时复制的容器。往一个容器中添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加完元素之后,再将容器的应用指向新的容器setArray(newElements);这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素,所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

源码分析

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        //显示获取锁
        lock.lock();
        try {
            //通过getArray()方法获取容器Object[]
            Object[] elements = getArray();
            //计算容器中元素的长度
            int len = elements.length;
            //将当前容器Object[]进行Copy,复制出一个新的容器Object[],容器大小加1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //添加元素
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            //显示释放锁
            lock.unlock();
        }
    }

二、CopyOnWriteHashSet

public static void setNotSafe() {
        //Set set = new HashSet<>();
//        Set set = Collections.synchronizedSet(new HashSet<>());
        Set set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(set);
            },String.valueOf(i)).start();
        }


    }
new HashSet<>().add("e");
hashSet底层实现是hashMap,但hashMap.put(key,value),为什么add("e")一个key就可以了?
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
//value是PRESENT恒定的

三、ConcurrentHashMap

public static void mapNotSafe() {
//        Map map = new HashMap<>();
//        Map map = Collections.synchronizedMap(new HashMap<>());
        Map map = new ConcurrentHashMap<>();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }

你可能感兴趣的:(Java多线程与并发,集合,并发)