深演智能Java面经

1.线程安全定义

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在调用代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个类就是线程安全的。

2.Spring全家桶基本概念

Spring

Spring是一个开源容器框架,可以接管web层,业务层,dao层,持久层的组件,并且可以配置各种bean,和维护bean与bean之间的关系。其核心就是控制反转(IOC),和面向切面(AOP),简单的说就是一个分层的轻量级开源框架。
为依赖注入、事务管理、WEB应用、数据访问等提供了核心的支持

SpringMVC

Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。
SpringMVC是一种web层mvc框架,用于替代servlet(处理|响应请求,获取表单参数,表单校验等。SpringMVC是一个MVC的开源框架,SpringMVC=struts2+spring,springMVC就相当于是Struts2加上Spring的整合。

Springboot

Springboot是一个微服务框架,延续了spring框架的核心思想IOC和AOP,简化了应用的开发和部署。
Spring Boot是为了简化Spring应用的创建、运行、调试、部署等而出现的,使用它可以做到专注于Spring应用的开发,而无需过多关注XML的配置。提供了一堆依赖打包,并已经按照使用习惯解决了依赖问题--->约定大于配置。

spring springboot开发区别

spring framework的Java Web开发模式:

  1. pom文件中引入相关jar包,包括spring、springmvc、redis、mybaits、log4j、mysql-connector-java 等等相关jar ...
  2. 配置web.xml,Listener配置、Filter配置、Servlet配置、log4j配置、error配置 ...
  3. 配置数据库连接、配置spring事务
  4. 配置视图解析器
  5. 开启注解、自动扫描功能
  6. 配置完成后部署tomcat、启动调试

springboot的Java Web开发模式:

  1. pom.xml配置需要引用的相关jar包
  2. yml中配置数据源,服务器,持久层
  3. springboot启动类加相关注解

SpringClod

Spring Cloud事实上是一整套基于Spring Boot的微服务解决方案。它为开发者提供了很多工具,用于快速构建分布式系统的一些通用模式,例如:配置管理、注册中心、服务发现、限流、网关、链路追踪等。

3.如果让你自己实现一个springboot怎样实现

ConfigurationProperties和AutoConfiguration。因为Spring Boot坚信“约定大于配置”这一理念,所以我们使用ConfigurationProperties来保存我们的配置,并且这些配置都可以有一个默认值,即在我们没有主动覆写原始配置的情况下,默认值就会生效,这在很多情况下是非常有用的。除此之外,starter的ConfigurationProperties还使得所有的配置属性被聚集到一个文件中(一般在resources目录下的application.properties),这样我们就告别了Spring项目中XML地狱。

创建自己的springboot starter

如果你想要自己创建一个starter,那么基本上包含以下几步

  1. 创建一个starter项目,关于项目的命名你可以参考这里
  2. 创建一个ConfigurationProperties用于保存你的配置信息(如果你的项目不使用配置信息则可以跳过这一步,不过这种情况非常少见)
  3. 创建一个AutoConfiguration,引用定义好的配置信息;在AutoConfiguration中实现所有starter应该完成的操作,并且把这个类加入spring.factories配置文件中进行声明
  4. 打包项目,之后在一个SpringBoot项目中引入该项目依赖,然后就可以使用该starter了

4.JVM调优工具使用经验

jstat查看内存回收概况,实时查看各个分区的分配回收情况,jmap查看内存栈,查看内存中对象占用大小,jstack查看线程栈,死锁,性能瓶颈,某个线程使用cpu过高导致服务整体慢等都可以通过在这些命令辅助Linux命令看出来。

5.volatile关键字

volatile的特性

可见性:对一个volatile变量的读,总能获取其他任意线程对该变量最后的写入。

有序性:JMM会限制volatile变量相关的编译器重排序和处理器重排序。

可见性的内存语义

volatile写的内存语义:当写入一个volatile变量的时候,JMM将线程工作内存中的该变量的值刷新到主内存。

volatile读的内存语义:当读取一个volatile变量的时候,JMM首先将该线程工作内存中的这个变量设置为无效,迫使该线程重新从主内存获取最新的有效值。

编译器对于有序性重排序的限制

  1. volatile读写不能被更改顺序
  2. 普通读写之前的volatile读不能被重排序到后面
  3. 普通读写之后的volatile写不能被重排序到前面

内存屏障实现有序性

LoadLoad屏障

对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

StoreStore屏障

对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

LoadStore屏障

对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

StoreLoad屏障

对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。 在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

JMM内存屏障的插入策略

volatile写操作前面插入一个StoreStore屏障

对比StoreStore屏障的定义,这里的volatile写是那个Store2,该屏障保证在volatile写执行之前,Store1的写入操作对其他处理器可见,那么可以得出,该屏障不仅保证了Store1已经执行完毕(有序性),也保证了可见性。

每个volatile写操作的后面插入一个StoreLoad屏障

对比StoreLoad屏障的定义,这里的volatile写是那个Store1,该屏障也保证了有序性和可见性。其他都是类似的。

每个volatile读操作后面插入一个LoadLoad屏障。

每个volatile读操作后面插入一个LoadStore屏障。

6.cas

cas用来保证操作的原子性操作

Unsafe类中的compareAndSwapInt,是一个native方法,该方法的实现位于unsafe.cpp中


cas.png

先想办法拿到内存地址,再通过Atomic::cmpxchg(x, addr, e)实现比较交换。e是原内存值,x为新值。这个函数在WindowsX86下实现如下:

inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
    int mp = os::isMP(); #判断是否是多处理器
    _asm {
        mov edx, dest  #变量内存位置放edx
        mov ecx, exchange_value  #要更新的值放ecx
        mov eax, compare_value #原内存值放eax
        LOCK_IF_MP(mp) #决定是否Lock
        #这句是真正的CAS操作
        cmpxchg dword ptr [edx], ecx
        # dword ptr 将 [edx] 强制类型转换成双字
        # cmpxchg 将 eax 里 内存原值 与(转换后的)对象值 比较
        # 如果相等,就是没别的线程在改变这个对象,那么这个线程就可以改了,将ecx值更新到这个对象。
    }
}

如果是多处理器,LOCK_IF_MP(mp)为cmpxchg指令添加lock前缀。反之,就省略lock前缀。(单处理器会不需要lock前缀提供的内存屏障效果)

7.单例模式

  1. 懒汉饿汉
  2. 枚举实现
  3. 双重校验

双重校验使用volatile关键字的原因

假如有两个并发线程a、b,a线程主动调用了静态方法getInstance(),这时开始加载和初始化该类的静态变量,b线程调用getInstance()并等待获得同步锁,当a线程初始化对象过程中,到了第二阶段即连接阶段的准备步骤时,静态变量doubleKey 被赋予了一个默认值,但是这时还没有进行初始化,这时当a线程释放锁后,b线程判断doubleKey != null,则直接返回了一个没有初始化的doubleKey 对象,问题就出现在这里了,b线程拿到的是一个被赋予了默认值但是未初始化的对象,刚刚可以通过锁的检索!

8.隔离级别

四种隔离级别,举例说明

mysql的隔离级别实现

MySQL默认使用rr的隔离级别

RR使用gap lock来解决幻读问题

MySQL RR/RC区别

简单来说,semi-consistent read是read committed与consistent read两者的结合。一个update语句,如果读到一行已经加锁的记录,此时InnoDB返回记录最近提交的版本,由MySQL上层判断此版本是否满足 update的where条件。若满足(需要更新),则MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)。semi-consistent read只会发生在read committed隔离级别下,或者是参数innodb_locks_unsafe_for_binlog被设置为true(该参数即将被废弃)。

对比RR隔离级别,update语句会使用当前读,如果一行被锁定了,那么此时会被阻塞,发生锁等待。而不会读取最新的提交版本,然后来判断是否符合where条件。

半一致性读的优点:
减少了update语句时行锁的冲突;对于不满足update更新条件的记录,可以提前放锁,减少并发冲突的概率。

9.什么时候OOM什么时候stackoverflow

OOM的情况

  1. 给应用程序分配的内存不够,只能通过增大内存来解决.
  2. 内存泄漏.有一部分内存"无用"了,但是因为编码问题导致的没有被垃圾回收掉,产生了泄漏,最终导致了内存溢出(OOM).

stackoverflow的情况

  1. 无限的递归,相当于你的参数无限多,那么栈放不下.
  2. 局部变量太大了,正常分配的栈空间(1M)不够用.

你可能感兴趣的:(深演智能Java面经)