持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天
集合类不安全
List不安全
ArrayList 在并发情况下是不安全的!
会报 java.util.ConcurrentModificationException 并发修改异常
解决办法:
- List list = new Vector<>();
- Listlist = Collections.synchronizedList(new ArrayList<>());
- List list = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略
多个线程调用的时候,list,读取的时候,固定的,写入(存在覆盖操作);在写入的时候避免覆盖,造成数据错乱的问题
CopyOnWriteArrayList比Vector厉害在哪里?
Vector底层是使用synchronized关键字来实现的:效率特别低下。
CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!
set不安全
和List、Set同级的还有一个BlockingQueue 阻塞队列;
Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;
解决方案还是两种:
使用Collections工具类的synchronized包装的Set类 使用CopyOnWriteArraySet 写入复制的JUC解决方案
// java.util.ConcurrentModificationException 并发修改异常
public class SetTest {
public static void main(String[] args) {
// Set<String> set = Collections.synchronizedSet(new HashSet<>());//解决方案1
Set<String> set = new CopyOnWriteArraySet<>();//解决方案2
for (int i = 1; i < 10; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
hasSet的底层是什么?
add 本质其实就是一个map的key,map的key是无法重复的,所以使用的就是map存储
hashSet就是使用了hashmap key不能重复的原理
//PRESENT是什么? 是一个常量 不会改变的常量 无用的占位
private static final Object PRESENT = new Object();
Map不安全
// java.util.ConcurrentModificationException 并发修改异常
public class MapTest {
public static void main(String[] args) {
//map 是这样用的吗?不是,工作中不用HshMap
//默认等价于: new HashMap<>(16,0.75);
//Map<String , String> map = Collections.synchronizedMap(new HashMap<>());
ConcurrentMap<String, String> map = new ConcurrentHashMap<>();
for (int i = 1; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
ConcurrentHashMap底层实现原理
- ConcurrentHashMap的整体架构
如图,这是ConcurrentHashMap在jdk1.8中的存储结构,它是由数据,单项链表,红黑树来构成,当我们去初始化一个ConcurrentHashMap实例的时候,默认会初始化一个长度等于16的数组,由于ConcurrentHashMap它的核心仍然是Hash表,所以必然会存在Hash冲突的问题,所以ConcurrentHashMap采用链式寻址的方式,来解决Hash表的冲突,当Hash冲突比较多的时候,会造成链表长度较长的问题,这种会使得ConcurrentHashMap中的一个数组元素的查询复杂度会增加,所以在jdk1.8里面,引入了红黑树的机制,当数组长度大于64并且链表长度大于等于8的时候,单向链表会转化成红黑树,另外随着ConcurrentHashMap的一个动态扩容,一旦链表的长度小于8,红黑树会退化成单向链表
- ConcurrentHashMap的基本功能
ConcurrentHashMap本质上是一个HashMap,因此功能和HashMap是一样的,但是ConcurrentHashMap在HashMap的基础上提供了并发安全的一个实现。并发安全的主要实现主要通过对于Node节点去加锁,来保证数据更新的安全性
- ConcurrentHashMap在性能方面的优化
如何在并发性能和数据安全性之间去做好平衡,在很多地方都有类似的设计,比如说cpu的三级缓存,mysql的buffer_poor,Synchronized的一个锁升级等等,ConcurrentHashMap也做了类似一个优化,主要体现在几个方面
- 在jdk1.8里面ConcurrentHashMap锁的粒度,是数组中的某一个节点,而在jdk1.7里面。它锁定的是Segment,锁的范围要更大,所以性能上它会更低。
- 引入红黑树这样一个机制,去降低了数据查询的时间复杂度,红黑树的时间复杂度实是O(logn)
如图,当数组长度不够的时候,ConcurrentHashMap它需要对数组进行扩容,而在扩容时间上,ConcurrentHashMap引入了多线程并发扩容的一个实现,简单来说多个线程对原始数组进行分片,分片之后,每个线程去负责一个分片的数据迁移,从而去整体的提升了扩容过程中的数据迁移的一个效率
- ConcurrentHashMap它有一个size()方法来获取总的元素个数,而在多线程并发场景中,在保证原子性的前提下去实现元素个数的累加,性能是非常低的,所以ConcurrentHashMap这个方面做了两个优化
如图,当线程竞争不激烈的时候,直接采用CAS的方式,来实现元素个数的一个递增
如果线程竞争化比较激烈的情况下,使用一个数组来维护元素个数,如果要增加总的元素个数的时候,直接从数组中随机选择一个,在通过CAS算法来实现原子递增,它的核心思想是引入数组来实现并发更新的一个负载