花呗面试题

java容器有哪些?哪些是同步容器,哪些是并发容器?

java中的容器也叫集合,是专门用来管理对象的对象

在这里插入图片描述

同步容器

  • Vector:Vector实现了List接口,Vector底层是一个数组,其对于数组的各种操作和ArrayList几乎一样,唯一不同的在于大部分线程不安全的方法都加了syncrhoized关键字去限定。

  • Stack:Stack底层也是一个数组,它继承于Vector类,很多方法也用syncrhoized关键字加了锁。

  • HashTable 实现了 Map 接口,它和 HashMap 很相似,HashTable对很多方法都加了syncrhoized关键字进行限定,而 HashMap 没有

  • Collections 工具类中提供的同步集合类: Collections类是一个工具类,相当于Arrays类对于Array的支持,Collections类中提供了大量对集合或者容器进行排序、查找的方法。它还提供了几个静态方法来创建同步容器类

在这里插入图片描述

并发容器

同步容器是通过syncrhoized关键字对线程不安全的操作进行加锁来保证线程安全的,其原理是使得多线程轮流获取同步锁进行对集合的操作,所以性能有所下降。

java.util.concurrent提供了多种并发容器,以:在原有集合的拷贝上进行操作,用修改后的集合替换原集合 的方式来达到并发且安全地使用集合类的目的。

  • CopyOnWriteArrayList - 线程安全的 ArrayList
  • CopyOnWriteArraySet - 线程安全的 Set,它内部包含了一个 CopyOnWriteArrayList,因此本质上是由 CopyOnWriteArrayList 实现的。
  • ConcurrentSkipListSet - 相当于线程安全的 TreeSet。它是有序的 Set。它由 ConcurrentSkipListMap 实现。
  • ConcurrentHashMap - 线程安全的 HashMap。采用分段锁实现高效并发。
  • ConcurrentSkipListMap - 线程安全的有序 Map。使用跳表实现高效并发。
  • ConcurrentLinkedQueue - 线程安全的无界队列。底层采用单链表。支持 FIFO。
  • ConcurrentLinkedDeque - 线程安全的无界双端队列。底层采用双向链表。支持 FIFO 和 FILO。
  • ArrayBlockingQueue - 数组实现的阻塞队列。
  • LinkedBlockingQueue - 链表实现的阻塞队列。
  • LinkedBlockingDeque - 双向链表实现的双端阻塞队列。

ArrayList和LinkedList的插入和访问的时间复杂度

ArrayList

  • 如果ArrayList没有扩张,那么插入的复杂度为O(1)
  • ArrayList访问复杂度为O(1)

LinkedList

  • LinkedList插入的复杂度为O(1)
  • LinkedList访问的复杂度为O(n)

java反射原理,注解原理?

java反射就是说在在程序运行状态下,我们都可以通过加载Class对象,从而获取该Class对象对应的类中所有成分信息,包含公有私有的属性、构造函数、方法等

注解可以放在类、方法、参数、属性、构造器上。通过反射机制获取被检查方法上的注解信息,然后根据注解元素的值进行特定的处理。

新生代分为几个区?使用什么算法进行垃圾回收?为什么使用这个算法?

在这里插入图片描述

(1) 新生代分区:Eden、From Survivor、To Survivor,大小比例为:8:1:1;

PS:

Eden 区

​ Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老 年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行 一次垃圾回收。

ServivorFrom

​ 上一次 GC 的幸存者,作为这一次 GC 的被扫描者。

ServivorTo

​ 保留了一次 MinorGC 过程中的幸存者。

(2) 当JVM无法为一个新的对象分配空间时候,会触发Minor GC。采用复制算法

(3) 因为新生代中大多数对象的生命周期都很短,存活对象的数量并不多,这样使用coping算法进行拷贝时效率比较高,不易产生碎片。

HashMap在什么情况下会扩容,或者有哪些操作会导致扩容?

新建的HashMap的默认初始容量为16,扩容因子为0.75,当元素数量>=16(当前容量)*0.75时触发第一次扩容,会将HashMap的容量扩大一倍。每次加入数据时都会判断当前的元素数与容量的占比,达到条件时触发扩容。

HashMap push方法的执行过程?

  • 判断当前表是否空(长度为0,或者null);
    • 如果是则调用resize()方法进行初始化操作;
  • 根据由Key计算出的Hash值对应的节点是否为空;
    • 如果为空,则将Value赋值给该节点;
    • 如果不为空,则判断Key是否相同
      • 如果相同,则替换为最新的Value;
      • 如果不同,则判断是否为树节点;
        • 如果是,则调用putTreeVal()以树节点的方式添加到表中;
        • 如果不是,则遍历链表,如果找到了相同的Key则替换Value;
          • 如果找不到则,在链表最后插入,判断是否达到了TREEIFY_THRESHOLD的阈值,如果打到了则进行树化;

结束

在这里插入图片描述

HashMap检测到hash冲突后,将元素插入在链表的末尾还是开头?

在jdk1.8之前是插入头部的,在jdk1.8中是插入尾部的。

由于1.8之前采用的是单向链表,采用头插法就是能够提高插入的效率,但是多线程情况下会产生死循环

1.8还采用了红黑树,讲讲红黑树的特性,为什么人家一定要用红黑树而不是AVL、B+树之类的?

​ 因为树的查找性能取决于树的高度,所以树的高度越低搜索的效率越高。红黑树是一种平衡树,

保证平衡性的最大的目的就是降低树的高度。

AVL树是更加严格的平衡,通常,AVL树的旋转比红黑树的旋转更加难以平衡和调试,因此可以提供更快的查找速度,一般读取查找密集型任务,适用AVL树。

B+树在数据库中被应用的原因就是B+树比B树更加“矮胖”,B+树的非叶子结点不存储数据,所以每个结点能存储的关键字更多。所以B+树更能应对大量数据的情况。
jdk1.7中的HashMap本来是数组+链表的形式,链表由于其查找慢的特点,所以需要被查找效率更高的树结构来替换。
如果用B+树的话,在数据量不是很多的情况下,数据都会“挤在”一个结点里面。这个时候遍历效率就退化成了链表。

ThreadLocal的原理和使用场景

​ 每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值

​ ThreadLocalMap 由一个个Entry对象构成的

​ Entry继承自weakReference<ThreadLocal<?>>,一个Entry由ThreadLocal对象和object构成。由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回

​ 当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
​ get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。
​ 由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。

使用场景:
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。(比如,参数无需依次从controller层传入service层再传入dao层)
2、线程间数据隔离(ThreadLocal为每个线程私有的)
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。

拓展

​ Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离

ThreadLocal内存泄漏原因,如何避免?

了解

  • 强引用:使用最普遍的引用(new),一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。
    如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使VM在合适的时间就会回收该对象。
  • 弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。

ThreadLocal的实现原理

​ 每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本

在这里插入图片描述

​ hreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为nul,而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉,但如果当前线程再迟迟不结束的话,这些key为nul的Entry的value就会一直存在一条强引用链(红色链条)

探讨

如果key使用强引用

​ 当hreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

key使用弱引用

​ 当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为nul,在下一次ThreadLocalMap调用set),get(),remove0方法的时候会被清除value值。

因此

ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

问题

​ 在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。

ThreadLocal正确的使用方法

  • 每次使用完ThreadLocal都调用它的remove() 方法清除数据
  • 将ThreadLocal变量定义成private static,这样就一直存在ThreadLoca的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值