多线程与高并发(七):常用容器的简单介绍

 

 

一、Collection

集合,继承了Iterable接口

1、Vector:

线程安全,几乎所有方法都是sync,效率较低。

 

2、ArrayList:

线程不安全,底层用数组实现。


 

3、Queue:

主要为了实现高并发而出现的接口,继承于Collection,主要提供了add、offer、poll、remove、element、peek等方法声明。 

方法介绍:

add:增加一个元索,则抛出一个IIIegaISlabEepeplian异常

offer:添加一个元素并返回true,如果队列已满,则返回false

remove:移除并返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常

poll:移除并返问队列头部的元素,如果队列为空,则返回null

element:返回队列头部的元素, 如果队列为空,则抛出一个NoSuchElementException异常

peek:返回队列头部的元素, 如果队列为空,则返回null

 

4、LinkedList:

线程不安全,底层用链表实现。继承了AbstractSequentialList类,实现了List和Deque接口。

 

5、CopyOnWriteArrayList:

线程安全,底层用数组实现,实现了List接口

通过写时复制保证线程安全:

① CopyOnWriteArrayList是通过“volatile数组”来保存数据的。一个线程读取volatile数组时,总能看到其它线程对该volatile变量最后的写入。就这样,通过volatile提供了“读取到的数据总是最新的”这个机制的保证。

② CopyOnWriteArrayList通过互斥锁来保护数据。在“添加/修改/删除”数据时,会先“获取互斥锁”,在修改完毕之后,先将数据更新到“volatile数组”中,然后再“释放互斥锁”;这样,就达到了保护数据的目的。

③ 它不存在扩容的概念,每次写操作都要复制一个副本,在副本的基础上修改后改变Array引用。CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能较低差。

④ 读操作和读取ArrayList相同,不加锁。

⑤ 适合使用在List大小较小,且读操作远远大于写操作的场景里,比如缓存。由于无法保证CopyOnWriteArrayList到底要放置了多少数据,故在写操作时可能会引起FGC或发生OOM。

⑥ 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照

 

6、CopyOnWriteArraySet:

线程安全,底层用数组实现,继承了AbstractSet类。

通过写时复制保证线程安全。类似于CopyOnWriteArrayList

 

7、LinkedBlockingQueue:

阻塞队列,线程安全,底层用链表实现。继承了AbstractQueue,实现了BlockingQueue接口。

通过对操作加ReentrantLock保证线程安全。

通过实现BlockingQueue接口,实现了put和take两个方法,这两个方法实现了阻塞。put往里装,如果容器装满则线程阻塞,take往外取,如果容器为空则线程阻塞。

注:LinkedBlockingQueue是一个无界队列,可以一直添加元素直到内存满了为止。

 

8、ArrayBlockingQueue:

阻塞队列,线程安全,底层用数组实现。

与LinkedBlockingQueue的区别为:ArrayBlockingQueue是有界的。容量满之后,put会被阻塞,add会抛出异常,offer会失败。

 

二、Map

1、HashTable:

线程安全,几乎所有方法都是sync,效率较低。

 

2、HashMap:线程不安全。

在jdk8以前是通过散列表(数组+链表)实现,jdk8开始用数组+链表+红黑树实现(当链表长度超过8时,链表转换为红黑树)。这是由于当Hash冲突严重时,在桶上形成的链表会变的越来越长,这样在查询时的效率就会越来越低(时间复杂度达到 O(N))

 

3、SynchronizedHashMap:

线程安全,通过Collections.SynchronizedMap(Map)获取,实现了Map接口,内部维护着一个map成员变量,所有操作都是加锁后对此map进行操作。

 

4、ConCurrentHashMap:

线程安全,无序,多线程中最常用的Map,底层通过CAS实现线程安全。

实现原理:

① 在jdk1.8以前,通过散列表(数组+链表)实现。

value、链表都用 volatile 修饰,保证了获取时的可见性。采用分段锁技术,其中Segment继承于 ReentrantLock。每当一个线程占用锁访问一个Segment时,不会影响到其他的Segment,理论上支持Segment数量大小的并发。

 

② 在jdk1.8后,开始用数组+链表+红黑树实现(当链表长度超过8时,链表转换为红黑树),这是由于当Hash冲突严重时,在桶上形成的链表会变的越来越长,这样在查询时的效率就会越来越低(时间复杂度达到 O(N))

value、链表都用 volatile 修饰,保证了获取时的可见性。抛弃了原有的 Segment 分段锁而采用了 CAS + synchronized 来保证并发安全性。

Node是ConcurrentHashMap存储结构的基本单元,继承于HashMap中的Entry,用于存储数据,Node是一个链表,但是只允许对数据进行查找,不允许进行修改。

TreeNode继承与Node,但是数据结构换成了二叉树结构,它是红黑树的数据的存储结构,用于红黑树中存储数据,当链表的节点数大于8时会转换成红黑树的结构,也就是通过TreeNode作为存储结构代替Node来转换成黑红树。

put方法:

1、如果没有初始化就先调用initTable方法来进行初始化过程。

2、如果没有hash冲突就直接CAS插入。

3、如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。

4、如果存在hash冲突,就加锁(锁链表头结点)来保证线程安全,这里有两种情况,一种是链表形式,就一直遍历直到找到对应的key修改value或从尾端插入新的Node,一种是红黑树就按照红黑树结构修改或插入。

5、如果Hash冲突时会形成Node链表,在链表长度超过8时,会将链表结构转换为红黑树的结构。

6、如果添加成功(通过判断oldValue是否为null)就调用 addCount 方法统计size,并且检查是否需要扩容。

 

 

 

5、ConcurrentSkipListMap:

线程安全,有序,底层用跳表实现,通过CAS保证线程安全。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值