【JaveEE】——多线程中使用顺序表,队列,哈希表

简介: 多线程环境下使用ArrayList(同步机制,写时拷贝),使用队列,哈希表(高频)ConcurrentHashMap(缩小锁粒度,CAS,扩容优化)


image.gif 编辑

阿华代码,不是逆风,就是我疯

你们的点赞收藏是我前进最大的动力!!

希望本文内容能够帮助到你!!

目录

一:多线程环境使用ArrayList

引入:

1:顺序表使用同步机制

2:套壳封装

3:写时拷贝

(1)添加/修改元素操作

(2)优点

(3)缺点

(4)使用场景

二:多线程环境使用队列

三:多线程环境使用哈希表(面试高频)

引入

1:ConcurrentHashMap

(1)缩小锁粒度

(2)使用CAS原子操作

(3)扩容的优化


一:多线程环境使用ArrayList

引入:

原来的集合类,大部分都是线程不安全的,但是有几个例外:Vector,Stack,HashTable(这几个类)但是现在官方已经不太推荐使用了,后续可能会删掉——因为哪怕是在单线程下也要加锁,这种情况不合理(往下看)

在这些类内部中,把一些关键的方法都加锁了,导致它们不仅在多线程场景下要加锁,而且在单线程场景下也要加锁。虽然JVM中有“锁消除”机制,但这也不是万能的,加锁带来的资源消耗依旧是不可忽视的(单线程下就没必要加锁了嘛)

1:顺序表使用同步机制

使用synchronized和ReentrantLock进行加锁,上一篇文章有提及两者的区别,往回翻翻~~

2:套壳封装

使用Collections.synchronizedList(new ArrayList)

因为ArrayList本身各种操作都是不带锁的,我们把它作为参数传入,相当于给ArrayList封装一下,套入Collections.synchronizedList()这个壳中,得到一个新的对象,这个新的对象调用关键的方法操作都是带有锁的

3:写时拷贝

使用CopyOnWriteArrayList

(1)添加/修改元素操作

如果我们往一个容器里面添加元素,我们不往这个容器中添加,而是先copy一份新容器,往新的容容里面添加或者修改元素

添加修改完元素之后,在将引用指向新的容器

(2)优点

①可以进行并发读在“读多写少”的场景下效率非常高(在引用指向新的容器之前,读操作都可以在旧容器上完成)

(3)缺点

①相应的顺序表如果太大,copy的开销也变高了

②“写操作”非常频繁,copy的频率就会非常高,资源的消耗和占用就比较严重

③不能第一时间读到新写的数据

(4)使用场景

服务器加载配置文件的时候,就会把文件内容解析出来放到内存的数据结构中,配置文件体积小,而且修改频率低

二:多线程环境使用队列

这边以前的文章有总结过就不再加以详述

主要以自己加锁和使用BlockingQueue为主

三:多线程环境使用哈希表(面试高频)

引入

在多线程环境下,Hashtbale是线程安全的,因为在Hashtable内部的关键方法中都有进行synchronized加锁操作。

但是HashMap就不行了,在数组的基础上还涉及到链表和树化

1:ConcurrentHashMap

于是我们就加以改进,引入了ConcurrentHashMap(并发HashMap),以下是我们的改进过程

(1)缩小锁粒度

①HashMap中的加锁

当我们尝试对HashMap中的不同链表下的不同元素进行修改操作的时候,就会触发锁竞争

因为这个锁是针对整个HashMap(this)而言的

如下图中我们想修改元素1和元素2,就会触发锁竞争,

重点:

但实际上修改不同链表上的元素操作,并不会触发线程安全问题(加大了加锁的频率,资源浪费)

只有在修改同一个链表下(相邻必触发:因为操作会涉及到同一个引用)元素可能会触发线程安全问题

于是我们在ConcurrentHashMap中进行了优化

image.gif 编辑

②ConcurrentHashMap中的加锁

锁对象为每个数组中的元素(链表的头结点),此时如果修改同一个链表下的元素,就会触发锁竞争。

理解:相当于把一个大锁拆分成了好多把小锁(这就是缩小锁粒度)

优点:不仅解决了线程安全问题,还降低了加锁的频率,节约了资源

注:这里的锁的数量虽然很多,但并不会增加太多的资源消耗,因为加锁对象(头结点)是现成的,不需要我们再去创建了

image.gif 编辑

(2)使用CAS原子操作

在ConcurrentHashMap中,比如针对哈希表中的元素个数的维护,我们使用CAS就可以减少一些加锁。

用synchronized加锁,咱们不知道加锁处于那种阶段(程度)的加锁——可能是偏向锁,轻量级化加锁,甚至是最后升级为重量级化加锁,这件事都是不可预估的

(3)扩容的优化

负载因子=元素个数/数组长度  ,0.75是一个扩容阈值指标

①HashMap扩容机制

如果数组元素个数太多会进行扩容,链表下元素个数太多会进行树化

扩容:创建一个更大数组,把旧Hash表上的元素一下子搬过去(一把梭哈),如果元素数量非常多,这里的copy操作就会非常的耗费时间,实际表现就是突然间某个操作非常卡

②ConcurrentHashMap扩容机制

扩容时,每次只搬运一部分元素,随着每次的插入/删除/添加/查找操作,都会搬运一部分元素。

内部机制:扩容时,有两份哈希表

插入操作——往新表上插

删除操作——新表旧表都删

查找操作——新表旧表都查

优点:确保每次操作耗费的时间都不长,避免出现卡顿的情况

缺点:整体扩容的时间变长了


相关文章
|
4月前
【数据结构】用栈实现队列
【数据结构】用栈实现队列
|
6月前
|
存储 测试技术
【数据结构】操作受限的线性表,队列的具体实现
【数据结构】操作受限的线性表,队列的具体实现
51 4
|
7月前
|
存储 数据安全/隐私保护 C++
数据结构01-线性结构-链表栈队列-队列篇
数据结构01-线性结构-链表栈队列-队列篇
数据结构— —队列(链式存储)
数据结构— —队列(链式存储)
【数据结构和算法】认识队列,并实现循环队列
【数据结构和算法】认识队列,并实现循环队列
|
存储 算法
【数据结构】顺序表的有序插入、扩容以及排序
【数据结构】顺序表的有序插入、扩容以及排序
185 0
数据结构线性表的顺序实现
数据结构线性表的顺序实现
|
存储 算法 前端开发
数据结构— —队列(顺序表实现)
数据结构— —队列(顺序表实现)
|
存储 编译器 C++
顺序表(数据结构)---排队啦!(二)
顺序表(数据结构)---排队啦!(二)
676 0

热门文章

最新文章