华资面经

华资一面

—— 人力面(挂)

1. Spring

1.1 Spring 的生命周期

推荐学习链接:https://blog.csdn.net/shijinjins/article/details/124071095

Spring 生命周期会经历过四个阶段,分别是实例化、属性赋值、初始化、销毁

其他的一些阶段就是通过AOP面向切面扩展来的,能够让你在实例化、初始化和销毁前后做一些逻辑。

初始化阶段,有一个特别重要的接口BeanPostProcessor

  • postProcessorBeforeInitialization(); // 初始化前调用
  • postProcessorAfterInitialization(); // 初始化后调用

初始化方式有三个

  • InitializingBean类的afterPropertiesSet方法
  • @PostConstruct注解标注的方法 // Spring支持javax包中的@PostConstruct,会在属性值注入成功后执行
  • Spring配置文件applicationContext.xml的标签属性init-method="initMethod"

这里提醒,@PostConstruct是通过SpringBeanPostProcessor#postProcessBeforeInitialization(Object, String)来完成调用的

如果是使用到了Spring自动注入来使用创建的Bean,那么如果使用反射的方式创建对象执行构造方法,先后顺序是这样的:

1
2
3
4
5
flowchart LR

A[服务器加载Servlet]--> B[Servlet构造函数]
B --> C["@Autowire"]
C --> D["@PostConstruct"]

反射的方式会在执行构造器后返回,所以你要通过反射使用Bean,可在@PostConstruct注解的方法保存该Bean,再去调用使用。

销毁方式有三个

  • @PreDestroy注解标注的方法 // // Spring支持javax包中的@preDestroy,会在销毁前执行
  • DisposableBean接口的destroy方法
  • Spring配置文件applicationContext.xml的标签属性destroy-method=;"destroyMethod"

这里提醒,@preDestroy执行是通过SpringDestructionAwareBeanPostProcessor#postProcessBeforeDestruction(Object, String)来完成调用的

简要步骤

1.当程序加载运行时会根据spring中配置文件找到bean配置的属性和方法,并通过java反射机制创建实例化bean对象。

Bean实现了BeanNameAware接口,执行了setBeanName方法,实现注入对象。

2.实现了BeanFactoryAware工厂接口,执行了setBeanFactory方法。

3.实现了ApplicationContext接口类,执行了setsetApplicationContest方法。

4.实现了BeanPostProcessor接口类,执行postProcessBeforeInitialization方法

5.实现了InitiliazingBean 执行afterPropertiesSet方法,并加载配置文件定义了init-method 则执行对应初始化方法BeanPostProcessor 执行postProcessorfterInitilization方法,完成 Bean的初始化,使得bean可以使用。

6.实现了DisposabileBean接口执行destroy方法,加载配置文件中的destroy-method方法销毁bean对象实例。

1.2 Spring 作用域

单例(默认Single)和多例(Prototype

单例在一个容器里面只存在一个对象,多例的Bean生命周期在容器产生时并不创建,对象使用时才创建,最后由JVM垃圾回收期回收

生命周期出生和销毁还可以自己指定,比如

  • init-method 对象出生之后立刻执行什么方法
  • destroy-method对象销毁之前执行什么方法

1.3 Spring 依赖注入方式

有三种:属性注入、构造函数注入、工厂方法(通过标签属性配置静态或实例工厂)注入

而基于这三种方式有多种实现:配置文件实现、注解实现、

2. 集合

集合的性质: 唯一性无序性确定性

唯一性指的是集合元素是不重复的、无序性指的是集合插入元素的顺序性、确定性是指集合中元素是确定的,不是相对性的。

集合类型按类型划分可以划分为可重复集合不可重复集

  • 可重复集合:List实现类
  • 不可重复集合:Set实现类、Map实现类(key

按照有序性(添加和访问)划分可以划分为有序集和无序集

  • 有序集:List实现类、TreeSetLinkedHashSetLinkedHashMapTreeMap
  • 无序集:HashSetHashMap

按照结构划分可以划分为列表ListSetMapStackQueue

List:ArrayListLinkedList

Set:HashSetLinkedHashSetTreeSet

Map:HashMapHashTableTreeMapLinkedHashMapCurrentHashMap

按照存储连续性划分可以划分为连续集和散列集

  • 连续集:ArrayList
  • 散列集:LinkedListMap实现类、Set实现类

3. 线程安全

3.1 线程安全的集合有哪些?

HashTable、CurrentHashMap、CurrentLinkedQueue、ArrayBlockingQueue、LinkedBlockingQueue

HashTable:它的方法都使用了重量级锁synchronized修饰来保证方法执行的线程安全。

CurrentHashMap:有JDK迭代版本;

  • 7使用了分段锁Segment(继承可重入锁ReentrantLock)实现线程安全,是真正意义上实现了并发
  • 8使用了synchronized + CAS,因为在1.6版本之后,synchronized一步步得到优化
    • 包含了偏向锁、轻量级锁、重量级锁
    • 处于偏向锁的对象,是指可以在获得该偏向锁的同一个线程中,多次执行同步代码块时相当于无锁
    • 而如果处于偏向锁的对象被其他线程获取了,偏向锁将不再偏向,就升级为轻量级锁
    • 升级为轻量级锁还是有机会继续偏向锁的,是怎么个原理?
    • 在对象头前8个字节中,存储了偏向线程id,前提得是该线程未获取锁对象,获取锁对象后设置的是当前线程id
    • 所以想让对象头上的偏向线程id重新偏向为当前线程id,得让该线程重复获取锁对象达到阈值20次才会偏向,不过只能偏向一次
    • 这就是锁降级,如果有第三个线程参与了,同样执行第二个线程的操作,将不再偏向(对象头偏向线程id重置为0)
    • 这是由再次升级为轻量级锁,前面是不发生竞争,如果多个线程发生竞争,竞争失败会自旋CAS再次尝试获取锁,如果还是失败
    • 这时升级为重量级锁,重量级锁将不再通过乐观锁CAS操作,而是会发生阻塞,必须等获得锁的线程释放锁才能让给下一个线程

CurrentLinkedQueue:使用了非阻塞的方式实现了线程安全,是一个无锁化的单向队列,使用到了CAS + for(;;),而且这种单向队列巧妙利用节点的不可重用 + CAS + next 指向本身 + GC垃圾回收,规避掉了ABA问题

  • 因为节点出队列并不会马上出队列,而是通过next指向自己让GC垃圾回收掉,这样有线程刻意去ABA操作时,发现还有旧值,没法做修改达到ABA的目的,这说专业点就是延迟出队。

LinkedBlockingQueue:与CurrentLinkedQueue非常像,也是一个无界队列,这样如果入队快于出队的情况容易出现栈溢出

ArrayBlockingQueue:是有界队列

4. 谈谈自己的项目