JUC并发容器类

ArrayList

ArrayList是一个可变长度的数组,构造时可以指定一个数组初始长度,不指定则默认为0

扩容机制
扩容过程
  • 创建一个新的数组,长度为newCapacity

  • 把旧的数组数据elementData拷贝到新数组

    elementData = Arrays.copyOf(elementData, newCapacity);

扩容算法

ArrayList第一次执行添加操作后默认size为10,后续扩容每次会扩容为原来的1.5倍

0 >>> 10 >>> 15 >>> 22 >>> 33

扩容性能很差,而且会占用内存,所以为了避免扩容,可以在使用的时候指定一个比较好的初始长度(capacity)

LinkedList

概述

LinkedList为双向链表结构,插入效率较高,取出数据效率比较慢

数据结构

在这里插入图片描述

数据节点

private static class Node<E> {
  	//节点储存的数据
    E item;
  	//下一个节点的引用
    Node<E> next;
  	//上一个节点的引用
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
插入逻辑

直接在指定位置添加一个新节点,prev指向前一个节点如果没有则为null说明是第一个节点,next指向后一个节点,如果没有则为null说明时最后一个节点,上一个节点的next和下一个节点的prev指向当前节点

取出逻辑

使用二分查找法遍历整个链表获取指定下标节点处的数据

CopyOnwriteArrayList

使用CopyOnwrite思想实现的ArrarList

CopyOnwrite思想

作用:优化读写锁的性能

读写锁存在的问题:

  • 当读操作很多的时候,写容易被饿死
  • 当写操作拿到锁后会有大量的读操作被阻塞

原理:

  • 写的时候,拷贝一份数据出来,修改完后才切换引用,读取新的对象
  • 保证线程安全,写的时候需要加锁,同一时刻只允许进行一个写操作
    在这里插入图片描述

优点:写的时候时仍然可以读,并且读到的不是脏数据

缺点:数据存在一定的延时、导致内存翻倍,容易内存溢出(OOM)

HashMap

数据结构

HashMap使用数组加链表的方式实现,数组的默认初始长度为16(可指定)

在这里插入图片描述

插入逻辑

通过hash(key)%length数据分片算法计算出数据存放位置的数组下标存入数组,当同一个下标处储存多个元素时,元素之间会使用链表的形式进行储存

取出逻辑

通过hash(key)%length数据分片算法计算出数据存放位置的数组下标,然后在使用key进行遍历下标处的链表取出对应的数据元素

存在问题

由于数组长度是固定的,当储存数据较多的时候会导致链表的长度过长,此时对链表进行遍历获取数据,必定会导致效率不高

解决方案

引入扩容机制解决

扩容机制
扩容条件

当size >= threashold且当前下标位置有元素时会触发扩容 (size >= length * 0.75 & table[index] != null)

如上图结构,当size达到了阀值,但是hash(key)%length计算出的下标为4时,也是不会触发扩容的

  • size:HashMap中元素的个数

  • length:数组的长度

  • threashold:触发扩容的阀值,threashold = length * 0.75

每次触发扩容会将数组长度扩充为原来的两倍(newLength = oldLength * 2)然后对当前HashMap中的元素使用new_length重新进行hash(key)分片储存

扩容操作比较消耗资源和内存,所以在使用的时候应尽量给定一个合理的出事长度,尽量避免扩容的发生

JDK1.8优化

JDK1.7并没有从绝对上解决链表的长度问题,JDK1.8把链表转换为红黑树,降低了由于链表长度过长导致的效率问题。当链表长度小于8的时候依旧使用链表的结构进行储存,当链表的长度达到8的时候就会转成红黑树。

在这里插入图片描述

ConcurrentHashMap

JDK1.7

使用分段锁提高并发量,降低锁力度(并发量与Segment长度有关)

在这里插入图片描述

JDK1.8

结构与JDK1.8的HashMap基本一致

  • 当储存位置有元素是,会锁住链表头部进行添加操作,保证线程安全

    约定:锁住链表头部就等于锁住整个链表

  • 当储存位置没有元素时,使用CAS操作添加元素,保证线程安全

在这里插入图片描述

总结

JDK1.7每个分段都会有很多数据,锁的粒度虽然比HashTable要好,但是仍然还是粒度很大,并发量是一定的。JDK1.8锁住的是链表,链表的粒度非常小,并发量相对来说大很多,而且是不固定的,通过扩容机制,链表越多,并发度就更高。

HashSet

基于HashMap实现的非线程安全的Set集合,HashSet本质上就是一个所有元素的value值均指向同一个Object的HashMap

CopyOnWriteArraySet

基于CopyOnWriteArrayList实现的线程安全的Set集合,CopyOnWriteArraySet本质上就是一个元素不重复的CopyOnWriteArrayList,即在执行添加操作(add)的时候判断元素是否存在,如果元素不存在才执行添加

ConcurrentSkipListSet

基于ConcurrentSkipListMap实现的线程安全、有序、效率高的Set集合,ConcurrentSkipListSet本质上就是一个所有元素的value值均指向同一个Object的ConcurrentSkipListMap

Queue队列

概述

Queue是队列数据结构的实现,分为阻塞队列和非阻塞队列。

Queue API

在这里插入图片描述

分类
  • ArrayBlockingQueue:线程安全的数组阻塞队列

  • LinkedBlocingQueue:线程安全的链表阻塞队列

  • ConcurrentLinkedQueue:线程安全的链表非阻塞队列,使用CAS不上锁进行线程安全操作,效率比LinkedBlocingQueue要高

  • SynchronizeQueue:同步队列,队列容量(length)永远为0,相当于只是一个阻塞通道

    • 单独执行put的时候会阻塞,当有poll或take方法去取元素的时候会解除阻塞
    • 单独执行offer时,元素会直接丢失并返回false,当有take在阻塞中的时候,offer会执行成功
    • 单独执行take的时候会阻塞,当有put货offer方法取存数据时会解除阻塞
    • 单独执行poll方法时,会直接返回null,当有put方法在阻塞着的时候,poll会取到put的元素
  • PriorityBlockingQueue:非先进先出队列,打破了先进先出规则,可以自定义排序规则,从而实现自定义出队规则
    通过传入comparator指定优先级规则:

    PriorityBlockingQueue<Student> queue =
        new PriorityBlockingQueue<>(5, new Comparator<Student>() {
            @Override
            public int compare(Student stu1, Student stu2) {
                if (stu1.age > stu2.age)
                    return -1;
                else if (stu1.age == stu2.age)
                    return 0;
                else
                    return 1;
            }
        });
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值