[Java基础]多线程高级3--容器

一.容器类接口关系图

在这里插入图片描述
(图片来源于网络)

二.HashMap实现分析

2.1HashMap数据结构

是链表散列的数据结构,即数组和链表的结合体
数组:存储区间连续,占用内存严重,寻址容易,插入删除困难
链表:存储区间不连续,占用内存宽松,寻址不容易,插入删除容易
HashMap综合应用了两种数据结构,实现了寻址容易,插入也容易

Entry0
Entry
Entry
Entry1
Entry
Entry
Entry2
Entry

横着是单链表,竖着是数组

2.2.1JDK1.8之前的并发问题

JDK1.8之前,HashMap的操作,比如addEntry(),resize(),removeEntryForKey()这些操作都没有加锁,导致多线程情况下会出现操作覆盖,操作丢失等问题

2.2.1JDK1.8并发问题

JDK1.8中的HashMap通过成员变量modCount来记录操作的版本
在这里插入图片描述在调用put(),remove(),ensureCapacity()这些会修改数据结构的方法中都会使modCount++
在获取Iterater对象的时候会把modCount赋值给迭代器的expectedModCount变量,此时的expectedModCount和modCount是相等的
迭代的元素的过程中如果调用hasmap自身的方法使集合发生变化,那么modCount肯定会变,此时modCount和expectedModCount肯定不相等,这时会抛出concurrentModificationException,终止迭代操作
由于HashMap是线程不安全的,所以我们需要手动地去使HashMap的线程安全

2.3解决HashMap并发问题的方法

-方法
1synchronized关键字
2Lock同步锁
3同步类容器
4并发类容器

重点讲解同步类容器和并发类容器

三.同步容器

3.1同步容器介绍

在Java中,同步类容器主要包括两类

类型特点
Vector,Stack,HashTable可以独立创建
Collections类中提供的静态工厂方法创建的类借助工具类创建,不可以独立创建

3.1.1 Vector

实现了List接口,实际上是一个数组,类似于ArrayList,但是vector里的方法都是synchronized方法,即实现了同步措施

3.1.2 Stack

也是一个同步容器,方法也都是synchronized的,实际上继承了Vector类

3.1.3 HashTable

实现了Map接口,和HashMap类似,但是都进行了同步,是线程安全的

3.1.4 Collections

是一个工具提供类(Collection是一个接口,它不是),它提供了大量的方法,比如对集合或容器进行排序,查找等操作,最重要的是它里面提供了几个静态工厂方法来创建同步容器类,有如下的方法:
在这里插入图片描述

3.2HashTable

HashTable在JDK1.1就有了,可以看主要方法put(),remove(),get()的源码实现来研究它的同步控制机制
在这里插入图片描述
在这里插入图片描述
可以看到它的方法都是加了synchronized关键字的,虽然实现了线程安全,但是效率变低了

三.并发容器

同步容器的方法都是加了synchronized关键字的,虽然实现了线程安全,但是效率非常低下,所以在Java5.0以后,开始对多线程进行了重写设计,引入java.util.concurrent包,这些是用来替代原来的非并发容器的

并发容器对应目标原理
ConcurrentHashMapHashMap代替HashTable,synchronizedMap,支持复合操作JDK6中加入一种更加细粒度的加锁机制,segment"分段锁",JDK8中采用CAS
CopyOnWriteArrayListArrayList代替Vector和SynchronizedList利用高并发往往是读多写少的特点,对读操作不加锁,对于写操作加锁,对于写操作先复制一份新的集合,在新的集合上面修改,然后把新集合的地址赋值给旧引用,并通过volatile保证其可见性
CopyOnWriteSetHashSet代替SynchronizedList在CopyOnWriteArrayList的基础上实现,不同的是由于set的无序性,在调用add操作时,调用CopyOnWriteArrayList的addIfAbsent方法,这个方法遍历当前数组,如果该数组已经存在这个值,那就不加了,如果不存在就加入数组的尾部并返回
ConCurrentSkipListMapTreeMap代替SynchronizedSortedMap(TreeMap)用了跳表的数据结构,SkipList是一种可以代替平衡树的数据结构,默认是按照Key升序的,SkipList让已排序的数据分布在多层链表中,以0-1的随机数选择数据向上攀升与否,通过“空间换时间”的一个算法,ConCurrentSkipListMap提供了一种线程安全的并发访问的排序映射表
ConCurrentSkipListSetTreeSet代替SynchronizedSortedSet内部基于ConCurrentSkipListMap实现
ConCurrentLinkedQueue不会阻塞的队列代替Queue基于链表实现的FIFO队列(LinkedList的并发版本)

3.1ConcurrentHashMap数据结构分析

3.1.1JDK1.7之前ConcurrentHashMap的数据结构

基于分段实现的,所有的数据先分段,每个段里面又有小的HashMap
在这里插入图片描述
(图片来来源于https://www.jianshu.com/p/39a57484932e)

3.1.2JDK1.8中ConcurrentHashMap的数据结构

不分段,先分数组,数组里面指向链表,如果链表元素个数小于8,那么这个链表是简单的链表,否则这个链表是红黑树的,数组的这个值指向红黑树的根节点
在这里插入图片描述
(图片来来源于https://www.jianshu.com/p/39a57484932e)

3.2ConcurrentHashMap同步实现分析

3.2.1JDK1.7之前ConcurrentHashMap的同步实现分析

以put()方法为例
这个put方法没有真正的加锁,只是在一堆计算之后拿到分片,然后再调用分片segment的put方法,而segment是一个可重入锁,segment的put方法里面先加锁再操作之后,最后解锁,通过这个方式来实现线程安全。

3.2.2JDK1.8中ConcurrentHashMap的同步实现分析

我们可以看到cas的操作,这个操作是用来放在数组中的元素的

在这里插入图片描述
而Synchronized关键字是用来放链表中的元素
在这里插入图片描述
由此可以看到,它在这两个地方的操作都保证了它是线程安全的,而具体后面的链表怎么放,我们可以看到
在这里插入图片描述
它会根据链表元素的个数来决定是单链表还是红黑树
在主流程之外,我们可以深入研究三个问题:
1)put如何初始化
initTable()方法,通过CAS操作变量sizeCtl,判断这个变量是否等于零,如果小于零说明没有拿到锁,如果等于零说明拿到锁了,就可以进行初始化了
在这里插入图片描述
2)如何扩容
在trypresize里操作,跟上面的类似,也是对变量sizeCtl的CAS的操作
在这里插入图片描述

3)如何帮助数据迁移
transfer方法
在这里插入图片描述
保证线程安全的操作是CAS操作和synchronized关键字来实现,在迁移之前都做了线程安全的操作
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值