JUC之集合不安全

624 阅读1分钟

ArrayList

这里记录一些线程与集合的内容,首先讲一下ArrayList

ArrayList底层是Object类型的数组,初始容量是10(jdk7之前,jdk8之后是空引用,到add之后会变成10,类似于懒加载的机制),其扩容的方式是每次扩容为之前的一半,比如10会扩容成15,15扩容成22,扩容用到的方法时Arrays的copyof方法,OK,接下来后进入主题。

  • 先编写一段代码
List<String> list=new ArrayList<>();
public class _List不安全 {
    public static void main(String[] args) {
    
        List<String> list=new ArrayList<>();
        //3个线程
        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();
        }
    }
}

运行之后会报一个异常java.util.ConcurrentModificationException 并发修改异常,原因是多线程并发争抢统一资源,且没加锁

解决办法在下面

Vector

List<String> list=new Vector<>();

内部加锁,有 synchronized 方法,同一时间段只有一个个线程,效率低性能差

Collections

这是Connections的静态方法,传入一个ArrayList。

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

CopyOnWriteArrayList

这个JUC包的类,实现了List接口,写时复制。底层是olatile Object[] array,其add方法里面加了锁ReentrantLock:

List<String> list= new  CopyOnWriteArrayList<>();

写时复制

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

HashSet与HashMap

  • HashSet 扩展点知识点,HashSet底层数据结构是HashMap(源码构造器里面 new HashMap()),其add方法实际上return map.put(e,PRESENT)==null; PRESENT实际上就是一个object常亮,所以实际上就是HashMap的keys。
    也就是说,hashset底层是hashmap的key,value默认添加object常量,固定写死,hashmap底层是数组链表红黑树,hashset的add()调用的就是hashmap的put()。同样,可以通过CopyOnWriteArraySetCollections.synchronizedSet(new HashSet<>())解决,其构造器其实new CopyOnWriteArrayList();

  • HashMap

HashMap(无序无重复)底层是数组+链表(单向)+红黑树,HashMap存的是node,node里面存的是Key-Value,HashMap初始容量为16,负载因子为0.75(到16*0.75的时候扩容),初始容量和负载因子可以通过构造器来改,每次扩容为原值得一倍。

解决办法:

 Map<String, String> map = new ConcurrentHashMap();

另外,在new HashMap的时候,可以指定初始值大小,即HashMap容器数组大小,这样做的好处是

  • 如果确定map需要存放多少数据(例如1000),就直接指定1000的大小(通常是2幂)
  • 这样就减少了使用初始大小(16)的map时,要不断扩容导致的性能下降问题