这些问题是在一面富士康时被问到的问题,面试整整问了50分钟,目前1面已过,还有2 3面,先记录下,下面的答案也是以我自己的理解总结的,完整的还需自己去查相关资料。
一.Volatile 关键字理解
1.保证线程的可见性,由于Java内存模型中是把主存中的复制一份到工作内存中,进行完操作,在回写到主存中,如果多个线程同时对一个变量进行操作,那么就可能出现数据不一致,也就是脏读,volatile关键字修饰了之后,每次对变量操作时,就会强制刷新到直接到主内存或者从主内存中取最新的值。
2.可以阻止指令重排,保证线程的一致性,jmm为了保证性能,会在不改变语意的前提下,会允许编译器和处理器对指令序列进行重排序,但是在多线程情况下,很多时候,执行顺序一旦改变就和我们预期的不一致的了,使用了Voltaile关键字,jmm就会插入内存屏障来限制重排序。
二.ConcurrentHashMap 实现原理
1.我们都知道HashMap是线程不安全的,而HashTab的get/put所有相关操作都是使用了synchronized关键字的,这就相当于给整个hash表加了一个大锁,多线程访问时,只有一个线程能进行操作,访问,其他线程只能阻塞,这样的实现代价非常大。而ConcurrentHashMap为了避免这种情况,使用了分段锁,一把锁只锁一段数据,开销就要小得多。
三.AtomicInteger 如何保证线程安全
1.AtomicInteger 是用来保证数字的线程安全的,存在于java.util.concurrent.atomic包下,其实现原理使用了cas乐观锁,在更新之前,先查询一下当前值,然后在做 update 的时候,以旧值作为一个修改条件。当提交更新的时候,判断对应记录的当前值与第一次取出来的库存数进行比对,如果当前库存数与第一次取出来的库存数相等,则予以更新,否则认为是过期数据。
这时候就会出现aba问题,即线程 1 查询 V 的值为 A 与旧值 A 比较,值相等。线程 2 查询 V 的值为 A 与旧值 A 比较,值相等,更新值为 B。线程 1 更新值为 A。这样, V 的值又被更新成 A 了。这个问题可以使用同是atomic包下AtomicStampedReferencel解决。
四.线程的生命周期
1. new ,创建对象,新建状态 2,调用.start方法,进入就绪状态。3.获得cup执行权,运行状态。4.丧失cpu执行权,阻塞状态,5.消亡,当执行完或者因异常退出,结束生命周期。
五.解释下死锁的形成
所谓死锁,是指多个进程或者线程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。举个例子,甲线程a资源,乙线程持有b资源,都想获取对方的资源,甲乙互相等待对方先释放资源,就造成了死锁。
(1).线程形成原因有两种:
1进程间推进顺序非法。
2.竞争资源
(2).产生死锁的4个必要条件:
1. 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
4. 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。
六.解释 Classloader 的双亲委派原则,以及如何打破双亲委派
1.所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回,只有父加载器无法完成此加载任务时,才自己去加载。一个自定义类类加载时使用 AppClassLoader 加载,AppClassLoader 的父加载器为 ExtClassLoader, ExtClassLoader父加载器为BootstrapClassLoad,BootstrapClassLoad存在于jvm中,所以我们类.class.getClassLoader()方法是看不到BootstrapClassLoad的,双亲委派是从下到上查找,再从上通知下加载。
2.打破双亲委派:自定义ClassLoader 继承ClassLoader ,重写 loadClass 方法,不调用父类的 findClass 就行
七. SpringBoot 的生命周期
初始化环境变量 -> 初始化环境变量完成,初始化上下文context完成 -> 应用刷新 -> 应用已启动完成 -> 应用启动好了 -> 应用启动失败
八. Spring boot Bean 的生命周期
四个阶段:1.Bean的定义 2.Bean的初始化 3.Bean的生存期 4.Bean的销毁
首先Spring在实例化Bean的时候,会先调用它的构造函数,进行Bean的实例化,然后进行Bean的初始化,Bean的初始化经过三个阶段初始化之前(applyBeanPostProcessorsBeforeInitialization),其次是进行初始化(invokeInitMethods),最后是初始化之后(postProcessAfterInitialization),这就是Bean的初始化过程;然后就开始利用Bean进行业务逻辑处理,最后容器正常关闭,Spring开始销毁Bean.
九. spring Boot 的自动装配原理
在我们的springboot启动类上的@SpringBootApplication里@EnableAutoConfiguration注解,这个注解就是开启自动配置功能的,里面有 @Import 一个类用于加载 spring.factories 文件中定义的相关类加入到spring的容器中,,都是以AutoConfiguration结尾来命名的自动配置类,它实际上就是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,而XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。
十. spring Boot 配置文件优先级
SpringBoot也可以从以下位置加载配置:优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置。
1.命令行参数,所有的配置都可以在命令行上进行指定;多个配置用空格分开–配置项=值java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc
2.来自java:comp/env的JNDI属性
3.Java系统属性(System.getProperties())
4.操作系统环境变量
5.RandomValuePropertySource配置的random.*属性值
6.jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
7.jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
8.jar包外部的application.properties或application.yml(不带spring.profile)配置文件
9.jar包内部的application.properties或application.yml(不带spring.profile)配置文件
10.@Configuration注解类上的@PropertySource
11.通过SpringApplication.setDefaultProperties指定的默认属性
十一. Eureka 的心跳机制
1.Register(服务注册): 当Eureka客户端向Eureka Server注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等。
2.Renew(服务续约): Eureka客户会每隔30秒发送一次心跳来续约。 通过续约来告知Eureka Server该Eureka客户仍然存在,没有出现问题。 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。 建议不要更改续约间隔。
十二. eureka 和 zookeeper 的区别:
1.根据cap理论,可用性,一致性,分区容错性只能三选其二,p分区容错性是必须保证的,zk是保证了ap,即一致性和分区容错性,而eureka保证是可用性和分区容错性。
2.zk为了保证数据一致性,在lader选举的时候整个服务会不可用,eureka虽然服务一直可用,但是数据可能不是最新,这就需要结合业务来决定使用哪一个。因为服务注册跟偏向于可用性,现在Alibaba基于dubbo开源了nacos,和eureka一样保证了可用性,也加入了spring cloud的阵营。
十三.feign 的原理
在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,会生成代理实例,注入到Spring IOC容器中。将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的http Request 请求。使用JAVA的动态代理机制,使得Java 开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用。
十四. Redis 的两种持久化方案及其区别
1.AOF记录redis每次的写,删除操作,追加在aof文件的末尾,当服务器重启时会执行命令来恢复原来的数据,是以文件存储方式存储的。如果出现宕机,aof恢复数据不会出现丢失,但是恢复大量数据时,恢复会比较慢,运行效率也会略低于RDB持久化方式。
2.RDB:是指在指定的时间间隔内将内存中的数据集快照写入磁盘,持久化文件,比aof小,恢复也比aof快,但是如果出现宕机,就会丢失这段时间间隔内的数据。
十五.Redis集群方式有哪些,及其各自特点
1.主从:主从模式即master,slave节点,master节点负责写,在通过slaveof命令或设置slaveof选项同步到slave节点,通常用来做容灾备份,读写分离,又分为1主1从,1主多从,级联三种。这种模式的弊端就是一旦master节点挂了,整个集群就无法写入了,需要人为手动的把一个saver节点提升为master节点。
2.哨兵:哨兵模式则是为了改变这种窘境,引入了Sentinel哨兵监控,以每秒一次的频率向master节点发送ping命令,确认master节点挂掉以后,就会自动提升一个slave节点为master节点,是redis的高可用解决方案。
3.集群模式:Redis-Cluster模式实现了去中心化,不在依赖于主节点,而是将存储的key通过hash值运算平均分配到已知的节点,每个节点的权重都是一样的,至少需要2N+1个节点,而每个节点至少也要有1个从节点,也就是说需要6个节点才能搭建Redis-Cluste集群,当需要查询时只需要查询其中任何一个点,Redis-Cluster会自动去查找在哪一个点上,当主节点挂了之后,从节点也会自动补上,这种模式采用了分段存储,主要适用于分布式和对内存容量有要求的大型系统中。
十六.释下缓存击穿和缓存穿透、缓存雪崩
1. 缓存穿透: 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:增加校验,当用户多次请求一个不存在的值时在缓存中增加一个key-null的缓存。
2.缓存击穿:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决方案:设置热点数据永远不过期。加互斥锁。
3.缓存雪崩:缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:设置热点数据永远不过期。过期时间加上随机数。
十七.InnoDB 引擎 ACID 的实现原理
acid是指的数据库的四大特性:原子性,一致性,持久性,隔离性
1.原子性:原子性的特点是事务被视为最小的不可分割单位,要么都成功,要么都失败回滚。在innoDB中实现原理是采用记录回滚日志(undo Log)的回滚方式,在进行事务操作时异步插入一条相反的语句。比如insert一条数据,就会有一条delete语句,当触发事务回滚时,则会执行相反的语句。
2.持久性( Durability ),持久性是指事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。mysql是内存中操作数据库,再写到磁盘中,由此内存,磁盘速度不一致,有可能操作完成了还未写到磁盘上,系统就崩溃了,在innoDB中是采用预写(redo log)日志的方式来保证一致性的。当 Commit 时,必须先将事务的所有日志写到重做日志文件进行持久化,待 Commit 操作完成才算完成。
3.隔离性,一致性:事务的隔离性要求每个读写事务的对象对其他事务的操作对象能互相分离,互不影响,隔离性原理则是用锁来实现的,为了保证一致性那么就要锁的介入。InnoDB使用锁为了支持对共享资源进行并发访问,提供数据的完整性和一致性。
十八. InnoDB的锁:
实现了如下两种标准的行级锁:
共享锁(读锁 S Lock),允许事务读一行数据
排它锁(写锁 X Lock),允许事务删除一行数据或者更新一行数据
行级锁中,除了S和S兼容,其他都不兼容。
InnoDB支持两种意向锁(即为表级别的锁):
意向共享锁(读锁 IS Lock),事务想要获取一张表的几行数据的共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(写锁 IX Lock),事务想要获取一张表中几行数据的排它锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
十九.MySQL 索引的数据结构
索引类似于书籍的目录,是帮助我们在使用mysql时更快的获取数据,MyISAM和InnoDB 索引的数据结构都是使用的B+Tree。
二十. Springboot异常处理机制
1.@ControllerAdvice 配合 @ExceptionHandler 处理,缺点:仅能处理进入controller后发生的异常
2.重写 ErrorAttribute,自定义响应内容,可以处理所有错误信息,原理是:spring 碰到错误后,会发送 /error 请求
二十一. ribbon 自定义负载均衡策略
实现AbstractLoadBalancerRule 类,重写 choose 方法
二十二. zuul 网关的隔离策略
1. 线程池隔离,每个路由都有自己的线程池,而不是共享一个。
2. 信号量隔离, 默认信号量时100
总结:线程池提供了比信号量更好的隔离机制,并且从实际测试发现高吞吐场景下可以完成更多的请求。但是信号量隔离的开销更小,对于本身就是10ms以内的系统,显然信号量更合适。
二十三. zuul 网关的动态路由
通过重写DiscoveryClientRouteLocator 类来实现,需要配置中心来动态改变配置,配置中心如:Spring-cloud-config-server 来手动调用 /refresh 接口,或 apollo 来自动改变配置,总之都是动态改变路由规则