ArrayList
ArrayList是一个可变长度的数组,构造时可以指定一个数组初始长度,不指定则默认为0
扩容机制
扩容过程
-
创建一个新的数组,长度为newCapacity
-
把旧的数组数据elementData拷贝到新数组
elementData = Arrays.copyOf(elementData, newCapacity);
扩容算法
ArrayList第一次执行添加操作后默认size为10,后续扩容每次会扩容为原来的1.5倍
0 >>> 10 >>> 15 >>> 22 >>> 33
扩容性能很差,而且会占用内存,所以为了避免扩容,可以在使用的时候指定一个比较好的初始长度(capacity)
LinkedList
概述
LinkedList为双向链表结构,插入效率较高,取出数据效率比较慢
数据结构
数据节点
private static class Node<E> {
//节点储存的数据
E item;
//下一个节点的引用
Node<E> next;
//上一个节点的引用
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
插入逻辑
直接在指定位置添加一个新节点,prev指向前一个节点如果没有则为null说明是第一个节点,next指向后一个节点,如果没有则为null说明时最后一个节点,上一个节点的next和下一个节点的prev指向当前节点
取出逻辑
使用二分查找法遍历整个链表获取指定下标节点处的数据
CopyOnwriteArrayList
使用CopyOnwrite思想实现的ArrarList
CopyOnwrite思想
作用:优化读写锁的性能
读写锁存在的问题:
- 当读操作很多的时候,写容易被饿死
- 当写操作拿到锁后会有大量的读操作被阻塞
原理:
- 写的时候,拷贝一份数据出来,修改完后才切换引用,读取新的对象
- 保证线程安全,写的时候需要加锁,同一时刻只允许进行一个写操作
优点:写的时候时仍然可以读,并且读到的不是脏数据
缺点:数据存在一定的延时、导致内存翻倍,容易内存溢出(OOM)
HashMap
数据结构
HashMap使用数组加链表的方式实现,数组的默认初始长度为16(可指定)
插入逻辑
通过hash(key)%length
数据分片算法计算出数据存放位置的数组下标存入数组,当同一个下标处储存多个元素时,元素之间会使用链表的形式进行储存
取出逻辑
通过hash(key)%length
数据分片算法计算出数据存放位置的数组下标,然后在使用key
进行遍历下标处的链表取出对应的数据元素
存在问题
由于数组长度是固定的,当储存数据较多的时候会导致链表的长度过长,此时对链表进行遍历获取数据,必定会导致效率不高
解决方案
引入扩容机制解决
扩容机制
扩容条件
当size >= threashold且当前下标位置有元素时会触发扩容 (size >= length * 0.75 & table[index] != null)
如上图结构,当size达到了阀值,但是
hash(key)%length
计算出的下标为4时,也是不会触发扩容的
-
size:HashMap中元素的个数
-
length:数组的长度
-
threashold:触发扩容的阀值,
threashold = length * 0.75
每次触发扩容会将数组长度扩充为原来的两倍(newLength = oldLength * 2)然后对当前HashMap中的元素使用new_length重新进行hash(key)分片储存
扩容操作比较消耗资源和内存,所以在使用的时候应尽量给定一个合理的出事长度,尽量避免扩容的发生
JDK1.8优化
JDK1.7并没有从绝对上解决链表的长度问题,JDK1.8把链表转换为红黑树,降低了由于链表长度过长导致的效率问题。当链表长度小于8的时候依旧使用链表的结构进行储存,当链表的长度达到8的时候就会转成红黑树。
ConcurrentHashMap
JDK1.7
使用分段锁提高并发量,降低锁力度(并发量与Segment长度有关)
JDK1.8
结构与JDK1.8的HashMap基本一致
-
当储存位置有元素是,会锁住链表头部进行添加操作,保证线程安全
约定:锁住链表头部就等于锁住整个链表
-
当储存位置没有元素时,使用CAS操作添加元素,保证线程安全
总结
JDK1.7每个分段都会有很多数据,锁的粒度虽然比HashTable要好,但是仍然还是粒度很大,并发量是一定的。JDK1.8锁住的是链表,链表的粒度非常小,并发量相对来说大很多,而且是不固定的,通过扩容机制,链表越多,并发度就更高。
HashSet
基于HashMap实现的非线程安全的Set集合,HashSet本质上就是一个所有元素的value值均指向同一个Object的HashMap
CopyOnWriteArraySet
基于CopyOnWriteArrayList实现的线程安全的Set集合,CopyOnWriteArraySet本质上就是一个元素不重复的CopyOnWriteArrayList,即在执行添加操作(add)的时候判断元素是否存在,如果元素不存在才执行添加
ConcurrentSkipListSet
基于ConcurrentSkipListMap实现的线程安全、有序、效率高的Set集合,ConcurrentSkipListSet本质上就是一个所有元素的value值均指向同一个Object的ConcurrentSkipListMap
Queue队列
概述
Queue是队列数据结构的实现,分为阻塞队列和非阻塞队列。
Queue API
分类
-
ArrayBlockingQueue:线程安全的数组阻塞队列
-
LinkedBlocingQueue:线程安全的链表阻塞队列
-
ConcurrentLinkedQueue:线程安全的链表非阻塞队列,使用CAS不上锁进行线程安全操作,效率比LinkedBlocingQueue要高
-
SynchronizeQueue:同步队列,队列容量(length)永远为0,相当于只是一个阻塞通道
- 单独执行put的时候会阻塞,当有poll或take方法去取元素的时候会解除阻塞
- 单独执行offer时,元素会直接丢失并返回false,当有take在阻塞中的时候,offer会执行成功
- 单独执行take的时候会阻塞,当有put货offer方法取存数据时会解除阻塞
- 单独执行poll方法时,会直接返回null,当有put方法在阻塞着的时候,poll会取到put的元素
-
PriorityBlockingQueue:非先进先出队列,打破了先进先出规则,可以自定义排序规则,从而实现自定义出队规则
通过传入comparator指定优先级规则:PriorityBlockingQueue<Student> queue = new PriorityBlockingQueue<>(5, new Comparator<Student>() { @Override public int compare(Student stu1, Student stu2) { if (stu1.age > stu2.age) return -1; else if (stu1.age == stu2.age) return 0; else return 1; } });