1.HashMap
1.数据结构是数组+链表
2.初始化默认大小是16,扩容因子0.75
3.put方法:
先将key进行hash计算然后根据数组大小取模,得到数据存储的数组索引位置,如果当前数组已经有值,判断一下key和当前key是否一样,如果一样替换,不一样则在链表后追加一个值
4.get方法:
key进行hash取模,查找对应数组,如果数组有多个数据,遍历查找到key是一样的数值,返回
5.什么时候扩容?
当前map的size每次插入数据都会size++,当新的数据要插入的时候,size>=0.7516=12,且数组位置已经有了值,就会扩容,每次扩容会将数组2,新建一个数组,size为旧数组两倍,然后将原来的数据,重新hash计算,存入新的数组
6.jdk7和jdk8的区别?
1)扩容判断,7是大于扩容域值,且当前位置有值扩容.8是只要大于扩容域值就会扩容.
2)8开始当链表长度达到8时,会转化结构为红黑树,而不是一直增加链表长度
7.hashmap为什么线程不安全?
因为例如:其中用到的size++无法保证原子性
8.可以使用hashtable,就是加synchronized关键字,但不推荐使用,因为高并发情况性能很低,这是源码注释推荐的ConcurrentHashMap
ConcurrentHashMap
jdk1.7
1.结构是segment初始化大小16,扩容因子0.75,segment数组结构,初始化时,只会初始化索引为0的位置插入一个segment对象,之后只有插入到新的数组位置,才会创建segment到对应数组位置.segment继承了ReteenLock可重入锁,本质也是一把锁
2.当第一个数据要插入,先hash计算到对应segment位置,segment锁住一个小型的hashmap结构,只有一个线程可以往这个hashmap里插入数据,插入数据的方式和hashmap一样,此处不再赘述
jdk1.8
1.判断第一个位置没有元素,会通过自旋+CAS的方式初始化数组,插入数据,也是自旋+CAS,只有一个元素可以插入成功,解决线程不安全的问题
2.1.7和1.8最重要的区别是8使用CAS来操作数组中的node,只有一个线程可以成功操作node,是无锁编程,且锁的数量不是预设的,而是有线程来才创建,7则是预设和map数量相同的sengment锁,一个是lock一个是cas的区别,8之后弃用了sengment,操作node节点下的链表,则是使用synchronized,因为CAS只能局限一个变量,无法控制多个变量的原子性
ArrayList
1.底层是数组结构,初始大小是10,第一次add时才会创建数组,每次add是顺序插入数据当达到数组大小会扩容,每次扩容是增加为1.5倍,然后将就得数组复制过来
2.arraylist不能一边遍历一边修改,set方法会判断当前修改次数是否和原来一样,如果中途修改就会导致不一致,那么就会抛并发修改异常
3.线程安全的CopyOnWriteArrayList;1.add方法使用lock()加锁,2.复制一个新的数组,3.修改新数组,4.覆盖旧的数组
优点是并发安全,缺点是占用两份内存,而且不能保证一致性,因为在复制写的过程中,其他线程读到可能不是最新值
在JDK中,获取线程安全的List,我们可以使用Collections.synchronizedList(List
https://blog.csdn.net/p_programmer/article/details/86027076
Set
1.set集合和list最大区别是数据不重复
2.hashset基于hashmap实现,底层初始化会创建一个hashmap,利用hashmap的key不重复实现
3.CopyOnWriteArraySet底层基于CopyOnWriteArrayList实现,虽然list可以重复,但是set却不能重复,这是为什么呢?
原因是,CopyOnWriteArrayList里有个addIfAbsent如果存在则新增,不存在则不新增实现
Queen
1.队列数据结构的实现,分为阻塞队列和非阻塞队列,特点是先进先出,有序
2.add方法如果已满抛异常,offer方法如果已满会返回boolean,告诉你插入数据是否成功,put方法,如果队列已满,会阻塞等待插入成功
3.take()取数据,如果队列为空,会阻塞等待有数据才返回,poll()不阻塞,没数据就返回null
3.LinkBlockQueen和ArrayBlockQueen区别在于一个是数组,一个是基于链表结构
4.ConcurrentBlockQueen使用的自旋+CAS机制来存放数据,区别于ArrayBlockQueen的lock(),使用无锁编程,效率较高
5.synchronizedQueen本身没有数据结构存数据,当put()时,会将自己挂起,然后下一个线程get()可以获取到这个数据,相当于快递员送快递,一直等你拿走,可以用在线程池中的缓存线程池场景