秋招面试复盘
- 注:本文仅用于记录笔者秋招的尽经历,发出来是为了push自己。
- 目前笔试:5;面试:2;offer:0。
目录
一
- 自我介绍
- 项目介绍
- 谈一下Java语言的看法;是否用过其他语言,说说它们之间的区别
- 使用多线程的场景;创建线程的方式;什么条件下要使用多线程
- 动态代理 和 静态代理
- 谈谈你对synchronized关键字的理解;synchronized 与 lock API的区别
- 使用过哪些设计模式;单例模式的实现(逐渐深入,到双重校验锁)
- 对Spring做扩展有哪些关键点(到现在还是一脸懵);Spring Bean的生命周期
- MySQL了解吗。对MySQL的看法;count()参数的区别
- 详细解释你在项目中的具体做法(具体到代码实现)
二
- 自我介绍
- 项目介绍
- redis缓存失效;缓存雪崩解决
- MySQL索引;SQL优化具体
- ArrayList 和 CopyOnWriteList
- ArrayList 和 LinkedList
- synchronized 和 lock API的区别
- volatile关键字;单例模式的双重检验锁
- MyBatis执行sql的全过程
- Spring循环依赖;Bean注入方式
- shiro工作流程
三
- 自我介绍
- 项目介绍
- 对面向对象的理解;多态是什么
- Java值传递还是引用传递;跨平台的理解
- 泛型的看法;类型擦除和解决
- 一个Integer的List中,放入String是否可以;怎么实现
- super和extend区别
- LinkedList和ArrayList区别;扩容机制;为什么是1.5
- fail-fast机制了解么
- HashMap如何实现;红黑树的理解;扩容;常见的hash算法
- JMM和JVM;运行时数据区;虚拟机栈和本地方法栈;Java创建对象的方法
四
- 增强for循环了解吗,谈谈特点
- HashMap有了解吗,用过哪几个构造方法,对他的提供的方法的底层实现有了解吗
回答整理(form笔者收集,仅供参考,欢迎指正!)
一
1.谈一下Java语言的看法;是否用过其他语言,说说它们之间的区别
Java语言共有
- 简洁有效,。Java语言省略了C++语言中所有的难以理解、容易混淆的特性,例如头文件、指针、结构、单元、运算符重载、虚拟基础类等。它更加严谨、简洁。
- 高可移植性,一次编译,全局运行
- 面向对象
- 解释型与编译型共存:.java编译.class,.class解释器操作
- 适合分布式计算。Java语言具有强大的、易于使用的连网能力,非常适合开发分布式计算的程序。Java应用程序可以象访问本地文件系统那样通过URL访问远程对象。
- 具有多线程处理能力
- 动态语言,允许程序动态的加载所需的类
java和python区别
Java有gc;Java一次编译全局运行;生态强大;适合做服务,python适合数据分析
2.使用多线程的场景;创建线程的方式;什么条件下要使用多线程
使用多线程的场景
创建线程的方式
继承Thread类;实现runnable;实现CallAble
实现 Runnable 接口和 Callable 接口的区别
Runnable接口 不会返回结果或抛出检查异常,但是Callable接口 可以。所以,如果任务不需要返回结果或抛出异常推荐使用Runnable接口 ,这样代码看起来会更加简洁。
什么条件下要使用多线程
1、 程序包含复杂的计算任务时
主要是利用多线程获取更多的CPU时间(资源)。
2、 处理速度较慢的外围设备
比如:打印时。再比如网络程序,涉及数据包的收发,时间因素不定。使用独立的线程处理这些任务,可使程序无需专门等待结果。
3、 程序设计自身的需要
WINDOWS系统是基于消息循环的抢占式多任务系统,为使消息循环系统不至于阻塞,程序需要多个线程的来共同完成某些任务。
3.动态代理 和 静态代理
静态代理:通过编写源代码的形式生成代理类
动态代理:通过反射在字节码运行期间,动态创建代理对象
4.反射获取类的私有属性或者方法
1.通过全限定路径或者.class反射到类
2.类.getDeclaredMethod(方法名称)获取方法
3.method.setAccessible(TRUE)获取私有权限【public忽略】
4.method.invoke()【调用方法】
5.谈谈你对synchronized关键字的理解;synchronized 与 lock API的区别
synchronized:
多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
修饰实例方法,修饰静态方法,修饰代码块.
构造方法不能使用 synchronized 关键字修饰。
synchronized 关键字底层原理属于 JVM 层面:
synchronized同步语句块的实现使用的是monitorenter和monitorexit指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置。
synchronized修饰的方法并没有monitorenter指令和monitorexit指令,取得代之的确实是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法。
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
synchronized 与 lock API的区别
synchronized: Java的关键字,在jvm层面上,Lock: 是一个接口,JDK层面.
锁的释放:synchronized: 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁。Lock: 在finally中必须释放锁,不然容易造成线程死锁。
锁的获取:synchronized: 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待。Lock: 分情况而定,Lock有多个锁获取的方式,大致就是可以尝试获得锁,线程可以不用一直等待(可以通过tryLock判断有没有锁)
锁的释放(死锁产生):synchronized: 在发生异常时候会自动释放占有的锁,因此不会出现死锁。Lock: 发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生
锁的状态:synchronized: 无法判断。Lock: 可以判断,trylock()
性能:synchronized: 少量并发好。Lock: 大量并发好
底层实现:synchronized: 底层使用指令码方式来控制锁的,映射成字节码指令就是增加来两个指令:monitorenter和monitorexit。当线程执行遇到monitorenter指令时会尝试获取内置锁,如果获取锁则锁计数器+1,如果没有获取锁则阻塞;当遇到monitorexit指令时锁计数器-1,如果计数器为0则释放锁。。。。。Lock: 底层是CAS乐观锁,依赖AbstractQueuedSynchronizer类,把所有的请求线程构成一个CLH队列。而对该队列的操作均通过Lock-Free(CAS)操作。
6.使用过哪些设计模式;单例模式的实现(逐渐深入,到双重校验锁)
单例
饿汉式:所谓饿汉式,就是直接创建出类的实例化,然后用private私有化,对外只用静态方法暴露。
class Singleton {
//私有化构造器
private Singleton() {
}
//内部创建对象实例
private final static Singleton instance = new Singleton();
//对外公有的静态方法
public static Singleton getInstance() {
return instance;
}
}
/************************************************************************************************/
懒汉式:所谓懒汉式,就是在需要调用的时候再创建类的实例化。
class Singleton { //线程不安全,起到了懒加载效果,但是只能在单线程使用,多线程会不安全,因为当多个线程并发同时判断instance为空时,就会相应的实例化多个对象。
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() { //调用时才实例化对象,懒汉式
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
class Singleton { //线程安全. 这样虽然解决了线程安全,但其实实例化操作只做一次,而获取实例(即getInstance)的操作是很多次的,把调用的方法加上同步,会大大降低效率
private static Singleton instance;
private Singleton() {}
//synchronized同步处理
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
双重检查:
class Singleton { //双重检查. 需要用到关键字volatile,防止指令重排。如果不用volatile关键字,就会和线程不安全情形一样,在if判断那会有并发
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) { //判断是否实例化
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance; //否则直接return
}
}
7.对Spring做扩展有哪些关键点(到现在还是一脸懵);Spring Bean的生命周期
Spring做扩展:
而在Spring中提供了一些很特别的接口,他们会在容器初始化或者Bean实例化的过程中的某些时间点【例如:bean定义加载完毕,bean实例化前后,bean初始化前后】被调用,从而完成功能的扩展,在某个时间点可以插入自定义的处理逻辑,这也就是Spring的扩展原理。
后置处理器(处理器分为两种,一种针对Bean的创建工厂,一种针对Bean
- BeanFactoryPostProcessor:Bean实例化之前执行.它的执行时机是在Bean定义全部解析及加载完毕【加载完毕指的是bean定义已经被放入到了bean定义注册中心beanDefinitonMap中】,可以在实例化Bean实例之前修改Bean的定义信息。例如:修改bean的类型,修改bean的作用域.
- BeanPostProcessor:Bean实例化(new)之后,初始化(设置各种属性)之前会被调用.,主要是用来拦截Bean的创建,点完成一些特定标识的检查,例如判断其是否实现了自定义的某个接口,然后对bean对象作出某种定制化操作。或者说对生成的对象进行包装,生成代理对象等等
Spring Bean的生命周期:
主要指的是 singleton bean
- 实例化 Instantiation
- 属性赋值 Populate
- 初始化 Initialization
- 销毁 Destruction
8.MySQL了解吗。对MySQL的看法;count()参数的区别
MySQL是一个关系型数据库管理系统,每个库下有类似一个个表格,表中有行有列,存储数据的。开源、多语言、支持标准SQL、可修改。
count()参数区别
count(*)、count(1)、count(列名)之间的区别
count()可以统计行数,也可以统计某个列值的数量。如果指定了列,则统计时要求列值是非空的(不为NULL),也就是只会统计有值的结果数。
count(**): 用于统计行数。这里的通配符 "*" 并不会扩展成所有的列,实际上它会忽略所有的列,直接统计所有的行数。
count(1): 用于统计行数。按照MySQL官方文档的介绍,count(1) 跟 count(*) 没有区别。
InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.
count(列名): MySQL会将列值取出来判断是否为NULL,如果不为NULL,则进行累加,最终统计的是指定列有值的结果数。
二
1.redis缓存失效;缓存雪崩解决
redis缓存穿透(key 在缓存和数据库中都不存在)
- 重在穿透吧,也就是访问透过redis直接经过mysql,通常是一个不存在的
key,发起为id为“-1”的数据或id为特别大不存在的数据。在数据库查询为null。每次请求落在数据库、并且高并发。数据库扛不住会挂掉。解决:
- 可以将查到的null设成该key的缓存对象放到缓存中,过期时间设置短些。
- 当然,也可以根据明显错误的key在代码逻辑层就就行
验证。- 加锁
- 其他等等,比如用布隆过滤器(超大型hashmap)先过滤。
redis缓存击穿(key在缓存中不存在,在数据库中存在)
- 缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,好像蛮力击穿一样。
解决:
- 可以使用互斥锁避免大量请求同时落到db
- 可以将缓存设置永不过期(适合部分情况)
- 布隆过滤器,判断某个容器是否在集合中
redis缓存雪崩
- 在这里,就是redis缓存key集体
大规模集体失效,在高并发情况下突然使得key大规模访问mysql,使得数据库崩掉解决:
- 通常的解决方案是将key的过期时间后面加上一个
随机数,让key均匀的失效。- 考虑用队列或者锁让程序执行在压力范围之内,当然这种方案可能会影响并发量。
- 热点数据可以考虑不失效
2.MySQL索引;SQL优化具体
目的就是为了加快数据库的查询速度,以空间换时间,B+树。
聚集索引和非聚集索引的根本区别是: 表记录的排列顺序和与索引的排列顺序是否一致。
聚集索引
- 查询快(数据和索引在一块)
- 修改慢(因为为了保证表中记录的物理和索引顺序一致,在记录插入的时候,会对数据页重新排序[因为在真实物理存储器的存储顺序只能有一种,而插入新数据必然会导致主键索引树的变化,主键索引树的顺序发生了改变,叶子节点中存储的行数据也要随之进行改变,就会发生大量的数据移动操作,所以效率会慢])
- 因为在物理内存中的顺序只能有一种,所以聚集索引在一个表中只能有一个。
非聚集索引
- (数据和索引分开存放)非聚集索引的叶子层并不和实际数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针方式
- 非聚集索引层次多,不会造成数据重排。所以如果表的读操作远远多于写操作,那么就可以使用非聚集索引
- mysql的Innodb中,主键索引就是聚集索引;myISAM没有聚集索引。
SQL优化具体
基础优化:
- 查询SQL尽量不要使用select *,而是具体字段
- 避免在where子句中使用or来连接条件
- 使用varchar代替char
- 尽量使用数值替代字符串类型
- 查询尽量避免返回大量数据
- 使用explain分析你SQL执行计划
- 是否使用了索引及其扫描类型
- 创建name字段的索引
- 优化like语句:
- 字符串怪现象
- 索引不宜太多,一般5个以内
- 索引不适合建在有大量重复数据的字段上
- where限定查询的数据
- 避免在索引列上使用内置函数
- 避免在where中对字段进行表达式操作
- 避免在where子句中使用!=或<>操作符
- 去重distinct过滤字段要少
- where中使用默认值代替null
高级SQL优化
- 批量插入性能提升
- 批量删除优化
- 伪删除设计
- 提高group by语句的效率
- 复合索引最左特性
- 排序字段创建索引
- 删除冗余和重复的索引
- 不要有超过5个以上的表连接
- inner join 、left join、right join,优先使用inner join
- in子查询的优化
- 尽量使用union all替代union
3.CopyOnWriteList
CopyOnWriteArrayList读取时不加锁,只是写入、删除、修改时加锁
- CopyOnWriteList 是一个写时复制的策略保证 list 的一致性,所以在其增删改的操作中都使用了独占锁 ReentrantLock 来保证某个时间只有一个线程能对 list 数组进行修改,这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁。其底层是对数组的修改,调用 Arrays.copyarray() 方法进行对数组的复制,在底层还是调用的 C++ 去进行的数组的复制 System.copyarray()
- 容器array用volatile修饰,保证了内存可见性,保证对该容器的读取一定是最后的写入; ReentrantLock与volatile的配合保证了容器的线程安全。
- CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单等场景。
- 缺点: 内存占用问题,数据一致性问题。
4.ArrayList 和 LinkedList
是否保证线程安全:
ArrayList和LinkedList都是不同步的,也就是不保证线程安全;底层数据结构:
ArrayList底层使用的是Object数组;LinkedList底层使用的是 双向链表 数据结构;插入和删除是否受元素位置的影响
ArrayList采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响;LinkedList采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位置的影响是否支持快速随机访问:
LinkedList不支持高效的随机元素访问,而ArrayList支持 。内存空间占用:
ArrayList的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)
5.MyBatis执行sql的全过程
MyBatis执行sql语句的过程中处处体现着动态代理的思想
- 首先后端程序调用service接口方法;
- service接口被 动态代理拦截指向serviceMapper的xml文件;
- mapperProxy代理会执行它的invoke方法调用sqlSession的Executor;
- Executor分为两种情况: 第一种情况:未被代理时先执行cacheExecutor,如果是update操作直接更新数据库,如果是查询
- 操作会先 查询缓存(一级二级缓存)命中缓存直接返回,未命中则被代理statementHandler拦截执行preparedStatementHandler方法;第二种情况就是Executor直接被代理statementHandler拦截;preparedStatementHandler在执行查询操作前可以被自定义拦截器拦截( MyBatis原生不支持分页查询功能,此处可以代理实现分页查询);
- 设置handler的参数;
- 执行sql查询语句具体操作,得到数据库返回数据;
- ResultSetHandler处理查询到的数据(此处也可以 被代理拦截,写入自定义的服务端数据处理操作);
- 返回数据到前端;
6.Spring循环依赖;Bean注入方式
Spring循环依赖
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A.这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件
Spring中循环依赖场景有: (1)构造器的循环依赖 (2)field属性的循环依赖。
解决:
- Spring为了解决单例的循环依赖问题,使用了三级缓存
- 1.Spring首先从一级缓存singletonObjects中获取。
- 2.如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。
- 3.如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取.
- 4.如果从三级缓存中获取到就从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
Bean注入方式
Bean注入的方式有两种,一种是在XML中配置,此时分别有属性注入、构造函数注入和工厂方法注入;另一种则是使用注解的方式注入 @Autowired,@Resource,@Required。