面试突击
1、如果线上机器突然宕机,线程池的阻塞队列中的请求怎么办?
导致队列中积压的任务会丢失
解决 : 提交任务之前先在数据库里插入这个任务的信息,并标以状态比如 : 未提交、已提交、已完成。待机器重启后,使用一个后台线程扫描表中已提交和未提交的数据,进行重新提交
2、谈谈你对Java内存模型的理解
每条线程有自己的工作内存,工作内存中保存了被该线程使用的变量的主内存副本。线程不可以直接操作主内存,不同线程不可以访问对方工作内存中的变量,线程间变量的传递都是通过主内存来完成。
将一个int a = 0 改为 1的流程包括:
- 从主内存中read到 a= 0
- 将a = 0 load到工作内存中
- 线程use工作内存中的a = 0将a设置为1
- 将a = 1 assign到工作内存中
- 工作内存将a =1 通过store、write进主内存
3、Java内存模型中的原子性、可见性、有序性?
- 原子性:操作不可拆分,同一时刻只有一个线程可以进行操作。
- 可见性:一个线程中某个变量的修改能马上被其他线程知晓
- 有序性:jvm虚拟机为了性能优化在单线程不影响结果的情况下会进行指令重排,有序性就是禁止指令重排
4、volatile工作原理?如何保证可见性?原子性为什么不能保证?
回答顺序:内存模型 ==》 原子性、可见性、有序性 ==》volatile
- volatile 和 原子性 : 不能保证,多线程会存在问题
- volatile 和 可见性 :可以保证,一个被volatile修饰的变量修改之后马上会写入主内存,并且让其他线程中改变量的缓存失效。那么其他线程要读取的时候就必须要去主内存中读取
- volatile 和有序性 :
5、happens-before/内存屏障是什么?
6、spring中bean是线程安全的吗?
思路 :先描述一下spring中的作用域
spring的作用域有五种 :singleton、prototype、request、session、global-session
默认的作用域是singlton,如果在某个实例中存在实例变量,那么多线程调用的时候就会出现线程不安全的情况。
7、spring事务的实现原理是什么?说一下事务传播机制?
事务的原理就是如果你加了一个@Transactional注解,此时spring就会使用AOP思想,对你的这个方法在执行之前,先去开启事务,执行完毕之后,根据你方法是否报错来判断是否执行回滚操作。
- propagation_required : 默认,如果没有事务就新建一个事务,如果有事务存在就加入
- propagation_supports : 如果有事务就加入事务,如果没有事务就以非事务方式运行
- propagation_mandatory:使用当前事务,如果没有事务就报错
- propagation_required_new : 新建事务,如果存在事务就将当前事务挂起
- propagation_not_supported:以非事务方式运行,如果当前存在事务,就将事务挂起
- propagation_never : 以非事务方式运行,如果存在就抛出异常
- propagation_nested : 外层事务如果回滚,内层事务回滚。但是内层事务如果回滚,仅仅回滚自己的代码
8、spring bean的生命周期
- 实例化bean :
- 属性填充:实例化后的对象被封装在BeanWrapper中,spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入
- 处理Aware接口:Spring接口会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
- 如果实现了BeanNameAware接口,会调用他的setBeanName(String beanId)方法
- 如果实现了BeanFactoryAware接口,会调用setBeanFactory()方法,传递的就是工厂本身
- 如果实现了BeanClassLoaderAware接口,会调用setBeanClassLoader方法
- BeanPostProcessor : bean自定义的一些前置处理,applyBeanPostProcessorsBeforeInitialization
- PropertiesSet 和 init-method方法的调用
- BeanPostProcessor :bean自定义的一些后置处理,applyBeanPostProcessorsAfterInitialization
9、spring涉及的设计模式?
- 工厂:
- 单例:
- 代理:
10、索引
b-树
B+树
- myisam叶子节点不存数据,存的是物理地址,然后根据物理地址到数据文件中去查找到对应的数据。
- innodb 默认会根据主键建立索引,称为聚簇索引。innodb的数据文件本身就是索引文件,聚簇索引的叶子节点存储了真实的数据,如果对非主键建立索引,其叶子节点存储的是主键的值,然后根据主键再查询到对应的数据,该操作称之为回表。
- 索引的原则 : 全值匹配、最左匹配原则、慎用like 百分号在右边、范围查找只有范围查找字段可用索引、不要使用函数修改列
11、mysql事务
ACID ---- 事务的隔离级别
- atomic :原子性,一堆sql要么一起成功要么一起失败
- consistency :一致性,针对数据一致性来说的。就是一组SQL执行之前是正确的,执行之后数据也必须是准确的。
- isolation : 隔离性,多个事务跑的时候不能互相干扰
- durability : 持久性,事务成功后, 对数据的修改必须永久生效。
事务隔离级别:
- 读未提交 read uncommited :A线程可以读取到B线程未提交的数据,这个叫做脏读
- 读已提交 read commited : A线程跑的时候,先查询一个值为1,这时候B将这条记录改为2,并提交了事务,此时A再次查询该条记录的时候就发现变为2,这个问题叫做不可重复读
- 可重复读 read repeatable :A线程在运行过程中,对于某个数据的值,无论读多少次都不会改变,哪怕B线程修改了该值并提交了事务。
- 串行化 : 不可重复读和可重复读都存在幻读的问题,假设表中只有一条数据,事务A查询全表查询一条数据,这时候事务B插入一条记录,事务A再次查询出现了两条。如果要解决幻读,就需要使用串行化隔离级别的事务,将多个事务串行起来,不允许多个事务并发操作
MVCC :多版本并发控制
- innodb在存储的时候,会在每行数据的最后加两个隐藏列,创建时间和失效时间,保存的值都是事务的id,innodb事务开始的时候都会被分配到一个事务id,依次递增
- 一个事务在查询的时候,innodb只会查询创建时间事务id<当前事务id,并且失效时间 > 当前事务的id的数据
- 当执行update操作的时候,innodb实际上将原有列的失效时间覆盖,然后新增一条创建时间为当前事务id的数据
- update操作表里肯定还是一条数据,那么事务是怎么查到之前的版本的呢?更新的时候都会在undo.log中记录一条对应版本回滚记录,然后我们只要根据当前事务id对当前值进行回滚操作,就可以看到我们应该看到的数据了。
12、数据库锁有哪些类型?锁是如何实现的?行级锁有哪两种?一定会锁定指定的行吗?为什么?悲观锁?乐观锁?使用场景是什么?mysql死锁原理以及如何解决?
- mysql锁类型一般就是表锁,行锁和页锁
- 表锁 : 意向共享锁:就是加共享锁的时候,必须先加这个共享表锁;还有一个意向排他锁,就是给某行加排他锁的时候,必须先给表加排他锁。这个表锁是引擎自动加的
- innodb行锁有共享锁(S)和排他锁(X)两种,多个事务可以加共享锁读同一行数据,但是别的事务不能写。排他锁就是一个事务可以写,别的事务只能读不能写。
- innodb在insert、delete、update的时候会自动给那一行加排他锁
- innodb不会自己主动加共享锁 ,必须自己手动加: select * from table where id = 5 lock in share mode
- 手动加排他锁 : select * from table where id = 5 for update;
悲观锁和乐观锁
- 悲观锁:总是害怕拿不到锁,总是先加锁
- 乐观锁:先查出来一条数据,修改的时候查看数据库是不是还是那个版本,如果还是那个版本就修改。不然就重新查出来再修改
死锁:
场景:
- 事务A : select * from table where id = 1 for update;
- 事务B : select * from table where id = 2 for update;
- 事务A : select * from table where id = 2 for update;
- 事务B : select * from table where id = 1 for update;
持有对方的锁,gg
解决方案 : 找下dba查一下死锁的日志
13、mysql优化
explain select * from table;
- table : 哪个表
- type(重要):
- all:全表扫描
- const : 读常量,最多一条记录匹配
- eq_ref : 走主键,一般最多一条记录匹配
- index :扫描全部索引
- range : 扫描部分索引
- possiable_keys : 显示可能使用的索引
- key : 实际使用的所以
- key_len : 使用索引的长度
- ref :联合索引的哪一列被用
- rows : 一共扫描和返回了多少行
- extra :
- using filesort :需要额外排序
- using temporary :构建了临时表,比如排序的时候。 using temporary常见于group by会出现,这种情况是最不能忍受的。因为创建临时表会需要消耗很大的时间,也是导致sql变慢的主要原因
- using where :就是根据索引出来的数据再次根据where来过滤结果
14、什么情况下会进入老年代?
- 熬过默认的15次垃圾回收
- 垃圾回收的时候s区放不下就进入老年代
- 大对象进入老年代