线程部分
1、多线程的实现方式有哪些?
这个题目在一面的时候基本上都会碰到吧,继承 Thread 类、实现Runnable、callable 接口,最后调用 的是 start() 方法来启动线程。
@Test
public void testStart() {
Thread B = new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
// 主线程启动子线程
B.start();
}
这里还有个知识点是 start() 跟 run() 方法的区别和联系。
直接调用 start() 方法,此时线程处于一个就绪(可运行)的状态,但是并没有真正的运行。而是得到CPU 的时间片后,开始执行 run() 方法,run() 方法里面的是我们的线程体。
我们直接 运行 run() 方法,它其实就是一个普通的方法调用,在主线程中执行,是不会开启多线程的。
2、描述一些线程死锁的情况?
两个线程在持有自己的锁的时候,还要去持有对方持有的锁时,由于别人的锁已经被对方持有,造成彼此等待对方释放锁的情况。
很多项目中是因为多线程访问数据库产生死锁。
建议大家在准备这个问题的时候能说出来产生死锁的条件、现象、解决办法等。
死锁条件 | 加锁顺序 多个线程以不同顺序获得相同的锁 | |
---|---|---|
现象: | 一旦发生死锁,锁住的一组线程不能再用了,无限等待,会导致整个应用程序活性降低、性能降低 | |
解决办法: | 事前防止 | 在编码时注意防止死锁的几个准则 相同的加锁顺序 |
事后恢复 | 如果发生死锁了,只能重启应用程序,才能拆开死锁
|
3、项目中有没有用过线程池 ?怎么用的 ?
一般直接用工具类execurors. newSingleThreadExecutor、newFixedThreadPool、newCachedThreadPool
但编程手册不推荐这么做,所以应该自己创建ThreadPoolExecutor对象,更灵活安全。
4、线程池的原理是什么样子?底层方法的参数分别是什么意思?
回答这个问题的时候,当时我卡住了。我知道这几个底层都是对 调用的 ThreadPoolExecutor ,但是我死活没有想起来名字,这时候面试官提醒了一下,然后说没关系的。
接着就问:“那你知道他的参数都有哪些吗 ?都分别代表什么意思吗 ?”
我回答的是 有个 线程的个数 和 线程存活的时间,其他的没说上来。然后面试官说:“没关系的”。
补充一下:线程池底层都是通过 ThreadPoolExecutor 来实现的。
public ThreadPoolExecutor( int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
几个参数的意思分别为:
-
corePoolSize:线程池里最小线程数
-
maximumPoolSize:线程池里最大线程数量,超过最大线程时候会使用 RejectedExecutionHandler
-
keepAliveTime:线程最大的存活时间,超过这个时间就会被回收
-
unit:线程最大的存活时间的单位
-
workQueue:缓存需要执行的异步任务的队列
-
threadFactory:新建线程工厂
-
handler:拒绝策略,表示当workQueue已满,且池中的线程数达到maximumPoolSize时,线程池拒绝添加新任务时采取的策略。DiscardPolicy:抛弃当前任务,DiscardOldestPolicy:扔掉最旧的,CallerRunsPolicy:由向线程池提交任务的线程来执行该任务,AbortPolicy:抛出 RejectedExecutionException 异常。
MyBatis 部分
5、mybatis 的 $ 与 # 的区别?
回答:他们两都可以来传递参数,不过 # 可以方式 sql 注入,而 $ 就是字符串拼接的方式处理,可能会有sql 注入的问题。
上面还有一个关键的点没有答出来,那就是 #{} 在预处理时,会把参数部分用一个占位符 ? 代替 ,变成了如下的 sql 语句:
-
select * from user where name = ?;
而 ${} 则只是简单的字符串拼接,在动态解析阶段就直接拼接成了 最终的sql 语句:
-
select * from user where name = 'zhouq';
6、$ 跟 # 的使用场景 ?
这个问题我没有怎么理解得到,然后回答的就是 $ 在拼接表名的时候用,其他时候传递参数值的时候用 #。
7、mybatis 的 dao 接口跟 xml 文件里面的sql 是如何建立关系的?
这里问到的时候比较蒙圈,然后回答的是:mybatis 会先解析这些xml 文件,xml 文件里面有命名空间 (namespace),这里可以跟dao 建立关系,然后 xml 中的每段 sql 会有一个id 跟 dao 中的接口进行关联。。。
然后面试官说: "如果 我有两个这个xml 文件 都跟这个dao 建立关系了,那不是就是冲突了?",然后,我认怂了。
mybatis 到这里就没了。
数据库
8、mysql 锁机制 ?
面试官问的是,你了解mysql 的锁机制么?我就只答出来一个行锁。然后其他的没想起,就认了,其他的忘记了。
建议你去了解了解还有表锁、页面锁 等等。
9、排它锁 & 共享锁你了解吗 ?
这个地方我想了一会,说平时了解得不多。实时上,平常我们的小业务系统基本上没有用到这些,可能有用到的地方,也没有去在意吧。
接着,面试官说了下面这个场景题,然后让出解决方案。
10、场景问题:在A线程处理一条数据,比如扣款,或者是更新状态时候,其他的线程比如 B 需要对它进行阻塞,不能够再对这条数据进行操作,包括查询也不行,得等A 线程处理完成以后,B才能进行处理。A 跟 B 是同样的业务代码产生的,非不同的业务。要使用数据库的锁来实现,怎么实现 ?
问这个问题的时候,面试官很耐心的解释了这个场景,然后问我有没有想起点什么来?其实就是想考察上面的关于数据库锁的问题。
11、mysql 索引是怎么实现的?
回答的是 B+ 树,接着面试官继续问:“能否大致描述一下 B+ 树的大致结构 ?”。这块内容没怎么了解,直接认怂了。
缓存相关
这块内容是我项目上写得有使用了多级缓存的方案,然后面试官就这一块问了下面的这些关于使用缓存可能会遇到的问题。
12、缓存击穿、缓存穿透 、缓存雪崩 ?
13、热点数据失效怎么解决?
这两个问题,以前好好了解过,但是没整理成自己的东西,面试的时候也说得云里雾里。
14、先删缓存还是先更新数据库,为什么?
这里我说的:是先删缓存,然后再更新数据库,但这是错误的,这里有非常大的问题。
想想这样一个场景:
如果一个线程 A 先把缓存删除了,然后去更新数据库,那么在它删了缓存还没有更新到数据库的这个中间时间,线程B进来了,发现缓存没有,就去读库,这时候还是读取还是旧的数据,然后又更新到缓存去了。此时A 才把新数据写到数据库。
此时就产生了一个典型的问题就是“双写不一致”。
关于这块问题的讨论:《缓存更新的套路》-陈皓老师
kafka
15、kafka 的架构,包含了哪些角色?
这个问题我开始不知道怎么回答,就说了个 Broker,然后面试官提醒了一下:“不是我们平常还有生产者,消费呀什么的吗 ?”额,我说还有生产者、消费者、主题呀等等。
这过程中面试官还提到说平常我们在搭建的时候要配置写什么东西呀等等,按照官网的介绍说也行。
这里还有其他的比如Partition、消费者组、还有一个主要的 就是 zk 了。
这里建议大家好好的把 kafka 里面的这些概念、属于、架构图好好自己画一下。不然真是关键时候真说不出来,但是他一提你又明白。这样子肯定是不行的,面试是你说,不是面试官说。
16、kafka 的最小工作单元?
这个问题问得也是蒙圈,其实就是说我们在写代码的时候,要用kafka的时候。我们需要使用那些最基础的组件,比如生产者、消费者、主题、偏移量 等等。
这个问题如果你们遇到,最好向面试官问清楚。
17、kafka 消息重复消费的问题?幂等怎么做的?
刚开始面试官说,你知道kafka 消息重复的问题吗?有没遇到。
我回答的是,会存在消息重复消费的问题。我们在消费数据这端做了幂等处理来解决。
然后面试官继续才问的是:幂等怎么来做的, 我说通过设置数据版本号,还有数据库唯一索引等等。
面试官:“ok”。
这个问题,如果你能告诉面试官产生重复消费的情况,比如说投递的时候重复了,消费的时候由于 offset 没处理好等等问题导致的话,我想可能会更好。
18、kafka ack 机制?集群中的ack 是怎么实现的?
这里我只回答上了 ack 机制是啥,但是实现原理没有回答上来。
Redis
19、Redis 中有哪些数据结构
平常使用得最多的是 String , 还有 List 、Hash、Set、ZSet 。
但是像Redis 为什么这么快这种问题,我认为你应该要去了解,其他小伙伴经常遇到。也就是多路复用是个什么玩意儿?
源码
20、这里面试官问 你平常有没有看过一些源码?框架的也行?JDK 的也行。
然后我说看了 HashMap 的源码,然后就巴拉巴拉的说了一哈大体的 put、get 流程 ,它的结构是什么样子的。
总结
上面的模块虽然顺序有变化,但是每个大块里面的问题都是按照顺序来的,基本上都是由浅入深、循序渐进的来问。
像数据库锁、线程池、缓存问题 这些内容几乎都是那种连环炮的形式,直到摸到你的底。
-
多积累输出:输出倒逼输入或者教会别人是最好的学习方式,光看不练,几天就忘。
-
先深后广:深入学习,而不是只停留在使用API 的层面,一块一块系统的深入了解,再去搞其他的。
-
建立知识体系:把自己学习的内容形成博客也好,什么导图也罢,记得把这些零散的内容,整理成自己的知识。
-
别抱有侥幸心理:别裸面,如果自己有整理的还是多看一下,多准备准备。
-
隔一段时间就去面试吧:出去面面才知道,哪些是需要去补充的。