写点什么

对线面试官 - Java 基础面试题【一】

作者:派大星
  • 2023-09-19
    辽宁
  • 本文字数:2091 字

    阅读完需:约 7 分钟

面试官:能简单说说 String、StringBuffer、StringBuilder 的区别吗?


派大星:可以,


  • 首先String是不可变的,如果尝试修改会新生成一个字符串对象,StringBuffer 和 StringBuilder 是可变的。

  • StringBuffer是线程安全的StringBuilder是线程不安全的。所以在单线程环境下 StringBuilder 效率会更高一些。


面试官:不错,那 ArrayList 和 LinkedList 有哪些区别知道吗?


派大星:


  • 首先从数据结构方面讲的话,两者是不同的,ArrayList 底层是基于数组实现的,LinkedList 底层是基于链表实现的。

  • 其次从应用场景来讲的话,两者也是不同的,ArrayList 更适合随机查找,LinkedList 更适合删除和添加。查询、添加、删除的时间复杂度也是不同的。

  • 从接口角度来说,两者都实现了 List 接口,但是 LinkedList 还额外实现了 Deque 接口,所以 LinkedList 还可以当做队列来使用


面试官:可以,但是上述 List 并非是线程安全的。如果要保证线程安全应该怎么办?


派大星:可以尝试使用CopyOnWriteArrayList,它可以保证线程安全。


面试官:那你能简单讲一讲它的底层实现原理吗?


派大星:可以,


  • 首先 CopyOnWriteArrayList 内部也是通过数组来实现的,在向 CopyOnWriteArrayList 添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作再原数组上进行

  • 并且写操作的时候会加锁,防止出现并发写入丢失数据的问题

  • 写操作完成之后会把原数组指向新数组

  • CopyOnWriteArrayList 允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是 CopyOnWriteArrayList 会比较占用内存,同时可能会产生读到的数据不是最新的。所以不适合实时性要求很强的场景。


面试官:不错,了解 HashMap 吗?知道它的扩容原理吗?


派大星:了解一些:但是在不同 JDK 版本上有所不同。JDK1.7 版本:


  • 会先生成新数组,

  • 然后遍历老数组中的每个位置上的链表上的每个元素

  • 接着取每个元素的 key,并基于新数组长度就,计算每个元素在新数组中的下标

  • 再然后会将元素添加到新数组中去。

  • 最后当所有元素都转移完了之后,将新数组赋值个 HashMap 对象的 table 属性即可


JDK1.8 版本:


  • 会先生成新数组

  • 接着会遍历老数组中每个位置上的链表或红黑树

  • 然后会进行判断如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去

  • 如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置

  • 统计每个下标位置的元素个数

  • 如果该位置下的元素个数超过了 8,则生成一个新的红黑树,并将根节点添加到新数组对应的位置

  • 如果该位置下的元素个数没有超过 8,那么则生成一个链表,并将链表的头节点添加到新数组的对应位置上

  • 最后当所有元素转移完了之后,会将新数组赋值给 HashMap 对象的 table 属性


面试官:不错,HashMap 是线程安全的吗?如果不安全体现在哪里?在实际应用中如果要保证线程安全又几种解决方案呢?


派大星:HashMap 不是线程安全的。如果在实际使用过程中想要保证其线程安全,主要表现在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失在jdk1.8中,在多线程环境下,会发生数据覆盖的情况可以采用 HashTable 或者是ConcurrentHashMap。但是 HashTable 效率过于低下,多数情况下可以采用 ConcurrentHashMap 即可。


面试官:那你简单说说 ConcurrentHashMap 是如何保证线程安全的呢?


派大星:在 JDK1.7 它的 ConcurrentHashMap 的解决思想是将散列表分为多个段,进而使用分段锁来降低多的粒度,因为锁的粒度越小事务的并行度越高。概括来讲:


  • 在 JDK1.7 中 ConcurrentHashMap 中使用了分段锁,Segment 继承与 ReentranLock,并将每个 Segment 对线作为锁,每个 Segment 对象中有一个 HashEntry 数组。

  • Segment 数组经初始化后便不再扩容,HashEntry 数组可以扩容

  • 使用预创建的思想,当线程想要进行 put 操作而获取锁时发现锁被占用,会先进行对节点的创建,以避免线程处于空闲状态

  • 扩容是在 HashEntry 中的 put 方法进行的,而当前 HashEntry 已经使用了 Segment 对象作为锁来保证线程安全,进而保证了扩容的线程安全


JDK1.8 中:


  • 引入了红黑树的数据结构,且不在使用分段锁,改用 Node 数组

  • 直接在散列表的每个头节点上使用CAS进行创建头节点或者使用Synchronized关键字加锁


面试官:不错,那你可以简单聊聊 ConcurrentHashMap 的扩容过程吗?


派大星:JDK1.7:


  • 1.7 版本中的 ConcurrentHashMap 是基于 Segment 分段实现的,每个 Segment 相当于一个小型的 HashMap

  • 每个 Segment 内部会进行扩容(注意这里是 Segment 内部不是 Segment 本身),和 HashMap 的扩容逻辑类似,先生成新的数组,然后转移元素到新数组中

  • 扩中的判断也是每个 Segment 内单独判断的。判断是否超过阈值


JDK1.8:


  • 1.8 版本的 ConcurrentHashMap 不再基于 Segment 实现,

  • 当某个线程进行 put 时,如果发现 ConcurrentHashMap 正在进行扩容那么该线程一起进行扩容

  • 如果当某个线程 put 时,发现没有正在扩容,则将 key-value 天假到 ConcurrentHashMap 中,然后判断是否超过了阈值,超过了则进行扩容

  • ConcurrentHashMap 是支持多个线程同时扩容,扩容之前也先生成一个新的数组

  • 在转移数组时,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或多组的元素转移工作。


如有问题,欢迎加微信交流:w714771310,备注- 技术交流  。或关注微信公众号【码上遇见你】。




发布于: 刚刚阅读数: 3
用户头像

派大星

关注

微信搜索【码上遇见你】,获取更多精彩内容 2021-12-13 加入

微信搜索【码上遇见你】,获取更多精彩内容

评论

发布
暂无评论
对线面试官 - Java基础面试题【一】_Java 面试题_派大星_InfoQ写作社区