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

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

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

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

一、CopyOnWriteArrayList

1.普通java集合类

List<String> 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<String> list = new Vector<>()();
//   List<String> list = Collections.synchronizedList(new ArrayList<>());
    List<String> 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<String> set = new HashSet<>();
//        Set<String> set = Collections.synchronizedSet(new HashSet<>());
        Set<String> 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<String, String> map = new HashMap<>();
//        Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
        Map<String, String> 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();
        }
    }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值