说说 ArrayList
并发场景下有什么问题
迭代器 fail-fast,遍历的同时修改会抛出异常。
并发访问可以考虑以下2种方式:
- 使用 Collections.synchronizedList,写操作会阻塞迭代
使用 CopyOnWriteArrayList,写操作会在新复制的数组上进行,不影响旧数组迭代
什么是 fail-fast
防止集合在迭代过程中,被其它线程修改。
记录修改次数 modCount,并发修改可能触发 fail-fast,抛出异常。
可能不触发,因为 Iterator 实现的 hasNext() 没有比对 modCount。对比 ArrayList、LinkedList
ArrayList 是动态数组,随机访问快,但 add 可能要扩容、移动元素,效率降低。
LinkedList 是双端队列,随机访问慢,但 getFirst、getLast、add 操作效率很高。说说 HashMap
为什么 capacity 必须是2的幂
因为分桶索引 index = (n - 1) & hash()。
当 n 是 2 的幂,(n - 1) 用二进制表示全都是1,只要 hash() 保证均匀分布,那么分桶的结果也均匀分布。为什么扩容总是2倍
为了避免再哈希。扩容两倍可以让元素留在旧索引,或移动到新索引 = 旧索引 + 旧 capacity
为什么 TREEIFY_THRESHOLD 是 8
hash() 取 hashCode 的高16位、低16位异或运算的结果,使其服从泊松分布。
当 hash 服从泊松分布,binCount 为 8 的概率小于百万分之一,因此树化概率不大,不用担心 HashMap 性能。为什么使用红黑树
红黑树的综合性能最佳,查询效率优于 BST,而修改效率优于 AVL。
并发场景下会有什么问题
链表使用头插法,resize 的时候会生成环。JDK8 使用尾插法解决,但插入效率降低,所以引入红黑树。
- 更新丢失。对象A、对象B 属于同一个 bucket,但不 equals,如果并发执行,后者可能会覆盖前者。
size异常。代码中 size++ 自增不能保证原子性,会导致size异常。
死锁形成的4个条件
互斥。线程独占资源,其它线程只能等待。
- 请求和保持。线程保持一个资源,但又请求了新的资源,而该资源已被其它进程占有。
- 不剥夺。线程不会主动释放资源。
循环等待。A等待B完成,B等待A完成,形成一个循环等待环路。
哪些类适合作为 key
使用不可变类 String、Integer,已经重写 equals()、hashCode()
使用可变类 List,那么 put 进去的值可能取不出来,因为 hashCode 是可变的如何设计一个可以作为 key 的不可变类
重写 equals()、hashCode(),要保证两个 equals 的对象一定拥有相同的 hashCode。
类 final,不允许继承
- 成员变量 private,不提供修改接口
- 构造器,使用深拷贝 (deep-copy) 初始化成员变量
说说 ConcurrentHashmap
// 参考文献区分 HashMap、TreeMap、LinkedHashMap
LinkedHashMap 继承 HashMap,保存了记录的插入顺序,从而实现 HashMap 的顺序遍历
TreeMap 实现 SortMap,按自然顺序、自定义顺序遍历参考文献