集合类线程安全吗?ConcurrentModification异常遇到过吗?如何解决?

集合类不安全的问题

1. ArrayList的线程不安全问题

1.1 首先回顾ArrayList底层

  • ArrayList的底层数据结构是数组
  • 底层是一个Object[] elementData的数组,初始化默认为空数组
  • 默认容量DEFAULT_CAPACITY为10,如果容量不够调用grow()方法,将容量调整为原来的1.5倍,核心代码为int newCapacity = oldCapacity + (oldCapacity >> 1);
  • 扩容过程是首先创建出来一个新数组,之后使用Arrays.copyOf(elementData, newCapacity)将原数组内容拷贝到新数组

回到本节知识,

1.2 为什么说ArrayList会存在线程不安全问题?

很简单的一个示例代码:

package com.yuxue.juc.collection;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class ArrayListDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },"Thread"+ i).start();
        }
    }
}

此时程序会报异常:

image-20210706210355260

出现了一个java.util.ConcurrentModificationException异常!

为什么会出现这样的结果?首先我们都知道,在调用ArrayList时,底层add方法是没有加synchronized即没有加锁的,当不同的线程调用方法时,会出现不安全的问题

1.3 为什么会出现这种问题?

并发修改这个list所导致的

一个线程正在写入,另一个线程来抢夺,导致数据不一致,并发修改异常

1.4 解决方案?

1.4.1 Vector

  • Vector底层加了锁,加锁数据一致性一定可以保证,但是并发性急剧下降!
  • ArrayList就是牺牲线程安全性才提出的
  • 但是Vector是在JDK1.0已经出现的,ArrayList在JDK1.2版本出现的
  • 所以用Vector可以但是效率太低,那么有没有其他的工具类可以满足?

1.4.2 synchronizedList

将代码改为以下的代码即可

List<String> list = Collections.synchronizedList(new ArrayList<>());

1.4.3 CopyOnWriteArrayList

List<String> list = new CopyOnWriteArrayList<>();//写时复制,读写分离

CopyOnWriteArrayList.add方法:

public boolean add(E e) {
  final ReentrantLock lock = this.lock;
  //加锁
  lock.lock();
  try {
    //获取原来的数组,保存副本为elements
    Object[] elements = getArray();
    //获取原数组长度
    int len = elements.length;
    //将其拷贝到新数组newElements,长度为原数组加1
    Object[] newElements = Arrays.copyOf(elements, len + 1);
    //第len上元素为新添加值e
    newElements[len] = e;
    //设置新数组为newElements
    setArray(newElements);
    return true;
  } finally {
    lock.unlock();
  }
}

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

2. Set的线程不安全问题

HashSet线程不安全,同样报错为java.util.ConcurrentModificationException异常!

解决的类或者方法:

  • Set<String> set = Collections.synchronizedSet(new HashSet<>());
  • Set<String> set = new CopyOnWriteArraySet<>();

其底层还是CopyOnWriteArrayList,因为其源码为:

public CopyOnWriteArraySet() {
    al = new CopyOnWriteArrayList<E>();
}

HashSet的底层是HashMap!

/**
* Constructs a new, empty set; the backing HashMap instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
  map = new HashMap<>();
}

初始容量为16,默认负载因子为0.75的标准的HashMap!

其中底层还有一个很重要的问题,当HashSet调用add(e)方法是,如果是HashMap,其Key为e,value值为什么?此时通过源码我们可以得到:

public boolean add(E e) {
  //key为e,value为PRESENT
  return map.put(e, PRESENT)==null;
}
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

3. Map的线程不安全问题

HashMap是线程不安全的,想解决可以用

  1. Hashtable
  2. ConcurrentHashMap
  3. Collections.synchronizedMap

3.1 HashMap和Hashtable区别?

HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度

  1. HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)
  2. HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好
  3. 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别
  4. 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
  5. HashMap不能保证随着时间的推移Map中的元素次序是不变的

3.2HashMap与ConcurrentHashMap区别?

准备回头写一篇博客专门总结,先留着 : )

posted @ 2021-07-07 14:45  y浴血  阅读(117)  评论(0编辑  收藏  举报