面试题 ④

1、synchronized实现原理

        synchronized能够修饰代码块、静态方法、实例,确保数据的可见性和数据操作的原子性和程序执行顺序的有序性

锁的状态:(java6之后就对sychronized做了优化,不再是单纯的重量级锁)

无锁:

        刚创建的对象为无锁状态

偏向锁:

        当一个线程获得了锁,锁会进入偏向模式,当这个线程再次请求锁时无需同步,用于减少同一线程多次获得锁浪费时间,适合没有锁竞争的场景

轻量级锁(自旋):

       当出现竞争时,锁的状态进入轻量级锁,适合线程交替执行同步块的场合,当存在同一时间访问同一锁就会导致失效,然后进入自旋状态,等待尝试获取

重量级锁(互斥锁):

        当轻量级锁自旋多次还获取不到时或竞争激烈时,锁的状态膨胀为重量级锁

       monitor对象监视器,是重量级锁的底层实现原理,就像一个房间,房间中含有被保护的数据,monitor保证每次只有一个线程进入该房间访问数据;monitor是由ObjectMonitor实现的(c++编写的),其中有很多参数如_owner(指向持有ObjectMonitor对象的线程地址)、_WaitSet(存放处于wait状态的线程)和_EntryList(存放处于等待锁阻塞状态的线程) 

        synchronized基于对象实现,java中的对象存储在堆内存中,包含对象头、示例数据和对象填充三部分,对象头中包含着MarkWord(包含四种锁信息、hashcode)和ClassPoint(.class文件),synchronized锁住代码块/方法,通过对象的monitor的取用(monitorenter)与释放(monitorexit)来实现同步/通过方法的class文件Access flags后边添加sychronized标识从而线程持有monitor-执行方法-释放monitor

2、对happens-before理解

        Happens-Before是一种可见型模型,JMM(Java 内存模型)通过Happens-Before关系向开发人员提供跨域线程的内存可见性保证,如果一个操作的执行结果对另一个操作可见,那么两个操作之间必然存在Happens-Before关系;

        Happens-Before关系只是描述结果的可见性,并不表示指令执行的先后顺序,也就是只要不对结果产生影响,仍然允许指令的重排序(指令重排序能提高执行效率)

Happens-before规则:

程序顺序规则:一个线程中的每个操作不管怎么重排序,线程执行结果不能改变

传递性规则:也就是 A Happens-Before B,B Happens-Before C推出 A Happens-Before C

volatile变量规则:对一个volatile修饰的变量的写happens-before于任意后续的读

监视器锁规则:一个线程对一个锁的释放锁操作,happens-before于后续线程的加锁操作

线程启动规则:线程A执行ThreadB.start()操作,之前的操作happens-before线程B中的任意操作

3、volatile实现原理

volatile的作用:

        可以保证多线程环境下共享变量的可见性(某线程对共享变量修改,其他线程可以立即看到修改后的值,不可见主要是由CPU的三级缓存导致的一致性问题在并行下导致的)

        可以屏蔽多线程环境下的CPU指令重排(多线程下CPU指令的编写顺序和执行顺序不一致,从而导致可见性问题,CPU提供的内存屏蔽指令就可以避免重排)

        对于volatile修饰的共享变量,JVM会自动增加一个#Lock汇编指令(将当前处理器缓存行数据刷写到系统内存,这个刷写到主内存的操作会使其他cpu缓存的该共享变量内存地址的数据无效),根据CPU型号自动添加总线锁或缓存锁,并在变量写操作前后设置内存屏蔽禁止指令重排,还有变量读操作后插入两个内存屏障禁止后面的读指令和写指令重排。

        volatile可以看成是轻量级的sychronized,volatile不能保证原子性,在操作本身为原子性的时候,使用volatile优于sychronized

总线锁:锁定CPU的前端总线,从而使同一时刻只能有一个线程去和内存通信

缓存锁:

总线锁导致CPU效率大大下降,缓存锁为了改进,只针对CPU三级缓存中的目标数据加锁

4、什么是第一,第二,第三范式

第一范式(1NF): 要求数据库中每个表必须具有原子性,即每个列的值都是不可再分的

第二范式(2NF): 满足第一范式的基础上,非主键列必须完全依赖于主键列

第三范式(3NF):

        满足第二范式的基础上,要求数据库中的每个非主键列都不能传递依赖于主键,即必须直接依赖主键

BCNF(Boyce-Codd范式):

        满足第三范式的基础上,任何非主键列都不能部分依赖于主键列。也就是说,主键确定的情况下,不能存在部分依赖于主键的情况

第四范式(4NF):

        满足BCNF的基础上,消除多值依赖(多值依赖指的是在一个关系模式中,存在一组属性与另一组属性之间的多对多关系,即其中一组属性的值可能对应多个另一组属性的值)

设置范式是用于规范化数据库模式,以避免数据冗余、插入异常、更新异常等问题。主要目的是提高数据库的数据组织、查询效率和维护性。

5、jdk8新特性有哪些

Lambda表达式:以更简洁的方式来表示匿名函数或未命名方法,并将这些函数作为参数传递给方法或存储在变量中,使得代码更简洁

方法引用:对调用方法已经实现的函数的Lambda表达式进一步优化,省去了参数的传递

stream流:提供了一种简化集合和数组操作的新方式,提供很多方法(排序、比较等)

Optional类:可以用来防止空指针异常,不确定是否为空的对象可以用Optional包装

添加了新的日期API:localtime、clock、localdate(都不可变,适用于多线程环境)

接口更新:接口中可以用default或static关键字实现方法,

函数式接口:只包含一个抽象方法的声明(只有函数式接口才能缩写成lambda表达式)

6、spring boot启动过程

加载启动类:加了@SpringBootApplication的启动类的main 方法中,通过运行SpringApplication.run()方法启动

【@SpringBootApplication是由@EnableAutoConfiguration(导入自动配置AutoConfigurationSelector类从而加载加了@Configuration的配置)、@SpringBootConfiguration(等同@Configuration)和@ComponentScan(自动扫描并加载符合条件的Bean)组成的 】

服务构建:构建SpringApplication

        1.将资源加载器、主方法类记录在内存;

        2.确定Web服务类型(Servlet-tomcat);

        3.加载初始化类:读取META-INF文件中注册初始化、上下文初始化、监听器的配置;

        4.通过运行栈(stackTrace)判断main方法所在的类(启动类本身);

进入run方法

环境准备:

        1.new BootstrapContext(启动上下文)

        2.调用启动注册初始化器中的初始化initialize方法(默认没有)

        3.将java.awt.headless设置为true(表示缺少显示器键盘等输入设备也正常启动)

        4.启动运行监听器(SpringApplicationRunListeners),然后发布启动事件(获取并加载spring-boot工程spring.factories配置文件中的EventPublishRunListener,引入监听器配置)

        5.构建可配置环境Enviroment(默认Servlet类型):加载系统环境变量、jvm系统属性到propertySources的内存集合中,便于之后取用;通过配置环境configureEnvironment方法将启动时传入的环境参数args进行设置,同时在propertySources集合首个位置添加一个空配置configurationProperties(后续使用)

        6.发送环境加载完成事件,刚加载的八个监听器会监听到,其中部分会进行相应处理,如环境配置后处理监听器会去加载spring.factories配置文件中的环境配置后处理器EnviromenPostProccessor(这里监听器用到了观察者模式,串行执行)

        7..将spring.beninfo.ignore=true(Bean元数据信息不加载,属性、方法、构造函数)用来改善应用性能,打印Banner图(启动横幅)

容器创建:(ApplicationContext)

        1.通过createApplicationContext来创建容器(上下文),首先根据服务类型(默认Servlet)创建容器ConfigurabaleApplicationContext,这个过程中会创建存放和生产bean实例的Bean工厂,创建用来解析@Component、@ComponentScan等注解的配置类后处理器ConfigurationClassPostProccessor,创建用来解析@Autowired、@Value等注解的自动注解Bean后处理器AutowiredAnnotationBeanPostProcessor

        2.通过prepareContext方法对容器属性初始化,先用postProcessApplicationContext方法设置Bean名称生成器、资源加载器、类型转换器等,执行上下文初始化(实现容器ID、警告日志处理、日志监听等)ApplicationContextInitializer,为容器注册启动参数、Bean引用策略、Banner、懒加载策略等

        3.通过Bean定义加载器将启动类等资源定义集合加载到BeanDefinitionMap(Bean定义池),便于后续根据Bean定义创建Bean对象

        4.发布资源加载完成事件

填充容器:自动装配Bean

        1.通过prepareRefresh方法,在已有的系统环境基础上准备servlet相关的环境Enviroment,通过初始化属性资源initServletPropertySources方法给servlet初始化参数servletContextInitParams和servletConfigInitParams赋值,然后通过validateRequiredProperties方法检验是否有必填的环境变量(可以在自定义初始化属性资源InitPropertySources方法中通过setRequiredProperties将某些环境变量设置为必填),最后完成监听器和事件初始化,环境准备完成

        2.调用obtainFreshBeanFactory方法(如果选择ClassPathXmlApplicationContext作为容器,会重构BeanFactory和重新加载Bean定义,而选择的servletWebServerApplicationContext作为容器不会进行任何处理)

        3.调用prepareBeanFactory方法加载类加载器BeanClassLoader、表达式解析器BeanExpressionResolver和配置文件处理器PropertyEditorRegistar等系统级处理器和两个Bean后置处理器(解析Aware接口的ApplicationContextAwareProcessor、处理自定义监听器注册和销毁的ApplicationListenerDetector),还会注册一些特殊Bean(BeanFactory容器本身、ApplicationContext、系统环境Enviroment、系统属性等)放入特殊对象池和单例池中

        4.调用PostProcessBeanFactory方法对BeanFactory进行额外设置或修改(定义request、session等servlet相关作用域scopes,注册与servlet相关的ServletRequest、ServletResponse、HttpSession等特殊类)

        5.执行InvokeBeanFactoryPostProcessors方法,执行BeanFactory后置处理器beanFactoryPostProcessor(其中包括配置处理器ConfigurationClassPostProcessor,该处理器加载所有@Configuration配置类,同时检索指定的Bean扫描路径componentScans,然后通过Bean扫描器ClassPathBeanDefinitionScanner中doScan方法扫描每个类,将所有扫描出来的Bean定义都放到Bean定义池beanDefinitionMap中,同样还会扫描加了@Bean、@Import等注解的类和方法,将他们对应的Bean定义也都放到Bean定义池,后续就能通过这些Bean定义构造相应的Bean对象了)

        6.通过registerBeanPostProcessors方法检索所有的Bean后置处理器,同时根据指定的order为他们进行排序,然后放入后置处理器池beanPostProcessor中,每个Bean后置处理器都会在Bean初始化之前和之后分别执行对应的逻辑

        7.通过initMessageSource方法和initApplicationEventMulticaster方法从单例池中获取两个Bean放在ApplicationContext中:messageSource(用于国际化,可以通过自定义messageSource这个Bean结合messages.properties配置文件进行多语言切换配置)、applicationEventMulticaster(用于自定义广播事件,可以通过publicshEvent方法进行事件发布)

        8.通过onRefresh构造并启动Web服务器,先查找实现了ServletWebServerFactory这个接口的应用服务器Bean(默认为tomcat),通过getWebServer方法构造一个Tomcat对象,通过start方法进行启动,web服务器开始运行

        9.通过registerListeners方法在Bean中查找所有的监听器Bean,将他们注册到上面的消息广播器applicationEventMulticaster中

        10.通过finishBeanFactoryInitialization来生产我们所有的Bean(构造对象-填充属性-初始化实例-注册销毁,详情见springbean的生命周期),Bean生成后放入单例池singletonObjects中

        11.通过finishRefresh方法构建并注册生命周期管理器lifecycleProcessor,同时会调用所有实现了声明周期接口Lifecycle的Bean中的start方法(当容器关闭时会自动调用对应的stop方法),发布一个容器刷新完成的事件,SpringBoot正式启动完成

7、什么是服务熔断、服务降级,它们的区别有哪些

        服务熔断和服务降级是微服务架构中的常用容错机制,用于处理服务调用时的异常

服务熔断:

        服务熔断是一种保护机制,用于当服务不稳定或故障时,防止对该服务的继续请求; 当服务调用失败时,断路器会打开,直接关闭服务,一段时间后断路器半开允许少量服务,如果成功率高,则关闭断路器恢复正常服务

服务降级:

        当服务故障时或性能下降时,提供一个备用处理方案或者返回默认值,来保证用户或客户端能够获得基本的响应,而不是长时间的等待或错误;

8、对ThreadLocal的了解

        ThreadLocal是另一种与加锁思路不同的解决多线程共享数据访问不一致问题的方法

        ThreadLocal本身并不存储数据,它使用了ThreadLocals属性(在ThreadLocal中定义的ThreadLocalMap对象),当调用ThreadLocal的set(T value)方法时,将自身的引用(this)作为key,把用户传入的值作为value存储到Map中,这就相当于每个线程的读写操作都是基于线程自身的一个私有副本,线程之间的数据是相互隔离互不影响,这样基于ThreadLocal的操作就不存在线程安全问题。(空间换取时间的思路,提高程序的执行效率)

        使用ThreaLocal要及时调用remove方法清理ThreadLocal变量,否则可能导致内存泄漏

9、mybatis缓存机制

        mybatis缓存就是将用户经常查询的数据保存到内存中,提高查询效率,mybatis提供了一级缓存和二级缓存两种机制来优化数据库访问(分布式环境下不如直接用Redis等,开发成本低、更安全)

一级缓存(本地缓存):

        用于保存用户在一次会话过程中查询的结果,用户一次会话中只能使用一个sqlSessiion,一级缓存自动开启不可关闭,默认先查Local Cache中,若没有再查数据库,并将数据放入Local Cache(Sqlsession持有的Excutor中的)

二级缓存(全局缓存):

        是mapper级别的缓存,针对一个表的查询结果的存储,不同的sqlsession是可以共享的,也就是同一个namespace下的操作语句都连接同一个Cache

会话:会话就是一次完整的交流,包含多次请求响应,发送请求的为同一个用户

sqlsession:用户与数据库进行一次会话过程中使用的接口

10、Java中的引用有哪些类型

        垃圾回收机制主要是看对象是否有引用指向该对象,而程序员通过指定不同的引用可以决定某些对象的生命周期,有利于JVM进行垃圾回收

强引用:就是普通对象的引用,永远不会被垃圾回收

软引用:当内存空间不足时,才会被垃圾回收,通常用来实现内存敏感的缓存

弱引用:不管内存是否足够,都会被垃圾回收

虚引用:不影响对象生命周期,提供一种确保对象被finalize以后,内存回收之前采取行动

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值