最近疫情稳定了,跳槽的换工作这块,又开始了,这不,小编公司的同事因为公司不涨工资的事情,已经开始踏上了重新面试,寻找更高级别的出路了,而前几天的面试,让他一次大好的机会被京东的线程面试题阻挡在了门外。
1.最基础的面试题
多线程,说实话,在大公司对多线程和JVM优化这些内容,都是非常看重的,因为一些大型的互联网公司不缺少写 CRUD 的初级工程师,而缺少的是那些对项目能够进行优化,并且对 CRUD 和系统能够做出优化的工程师,所以在大公司面试的时候,很少有会去问那些比较简单的,而且大家都会的面试题的,比如:
- 线程的创建方式?
- 实现多线程的方式?好像和上面是一个答案哈,但是不影响,问法不同,回答一样。
- 在 Java 中,如何跳出当前的多重嵌套循环?
- String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?
说实话,大家在面试的时候被问到这种问题的,几乎是百分之九十九的程序员都是准备过的,而且也都是能够回答的比较完善的,小编绝对相信这一点,毕竟都是“有经验的开发人员”,大家说是不是?
2.进阶面试题
而再往后就是真的开始进阶面试题了,而小编的同事也是因为这些进阶的面试题,被京东拒之门外了,那么小编就来带大家看看到底是什么样子的面试题,会让小编的同事被如此的“摧残”。
2.1面试官第一问
面试官:你了解IO流么?
在小编的印象中,一般面试大厂都有一个很经典的套路,那就是一个知识点,问到你回答不上来为止,而像百度,阿里,腾讯这些大厂很多刚开始问得问题都是非常的基础,但是接下来,分分钟让你在一个问题上自闭。
IO流?了解呀,Java中的流,可以从不同的角度进行分类。按照数据流的方向不同可以分为:输入流和输出流。按照处理数据单位不同可以分为:字节流和字符流。
- 字节流:一次读入或读出是8位二进制。
- 字符流:一次读入或读出是16位二进制。
Jdk提供的流继承了四大类:InputStream(字节输入流),OutputStream(字节输出流),Reader(字符输入流),Writer(字符输出流)。
然后分别在说一下都有哪些类一般这个问题就已经算是回答完成了。
- FileInputStream(字节输入流),FileOutputStream(字节输出流),FileReader(字符输入流),FileWriter(字符输出流)
2.2面试官第二问
面试官:字节流与字符流有什么区别?
这问题问出来,感觉要和第一个问题差不多是不是,而后面面试官的一句话再次出口,
- 字节流和字符流哪个好?怎么选择?为什么这么选择?
关于字符流和字节流的区别,字符流和字节流的使用非常相似,但是实际上字节流的操作不会经过缓冲区(内存)而是直接操作文本本身的,而字符流的操作会先经过缓冲区(内存)然后通过缓冲区再操作文件。
那么怎么去选择?为什么选择?这种时候就是比较区分看业务情况了,因为字符流是对字节流的包装,而我们的操作如果需要频繁的使用 IO 来处理字符串,那么我们一般通常使用字符流来进行,比如读写个 txt 的文档之类的,因为字符流具备缓冲区,能提高我们的工作性能,而如果是操作磁盘文件,比如说图片的时候,我们都是使用字节流的,这些是需要根据实际业务场景进行区分和使用的。
回答完这些之后,小编的同事很庆幸,当时感觉自己这个问题应该回答的还不错的时候,面试官接下来的操作,让小编的同事又一次陷入了难受的境地。殊不知这是一环套一环,环环相扣,让你自闭。
2.3 面试官第三问
面试官:你既然了解 IO ,那你对 IO 模型肯定也有所了解,那你说说你对 IO 模型理解的部分吧。
这句话同事在给小编说的时候,小编第一反应就是同事这个问题,可能回答的会不太好,接下来的坑自己可能要跳下去了,因为 IO 模型,这块的内容,讲解起来有时候会用很长很长的时间,对于理解的人,可能简短的几句话就能把这些模型讲清楚,对于不理解的人,可能只能是死记硬背来给面试官复述。
IO 模型:
阻塞 IO 模型
阻塞 IO 模型是最经典的一种,其实说白了就是在我们进行数据读写的时候,会出现阻塞的情况,当用户线程发出 IO 请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出 CPU。其实如果这么解释的话,不是很形象,我之前看过一个网友说的烧水的案例,非常的给力,这个小情景是这样的,在很久之前,电热水壶还没有普及的时候,那是后烧水不都是放在火上,等水烧开嘛,而阻塞就相当于,你把水放到煤气灶上烧着,然后你就再旁边看着,这时候你只能坐在水壶前面等,你什么的事情都做不了,这时候就和现在是一个状态,属于阻塞的状态,如果这么理解的话,这个阻塞是不是就很好理解了。
非阻塞 IO 模型
当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。
这时候就相当于是有人在不停的问你,水烧开了么,水烧开了么?水烧开了吗?这直到水烧开,也就是直到这个内核数据准备就绪的时候,就能够完成读写。
多路复用 IO 模型
多路复用 IO 说实话,小编一开始的时候看到这个?多路复用,哎呦,这个词这么高大上?啥是个多路复用,但是经过一系列的观察之后,很多地方都能通俗的去解释这个多路复用,比如说我们家里的电话线,你在上网的时候,来了个电话,你能接吧,你接电话的时候还不忘在网上和朋友日常胡吹,这情况就可以称之为多路复用,那么多路复用IO模型是个什么意思呢?
来自百度的解释是这样说的:I/O是指网络I/O,多路指多个TCP连接(即socket或者channel),复用指复用一个或几个线程。意思说一个或一组线程处理多个TCP连接。最大优势是减少系统开销小,不必创建过多的进程/线程,也不必维护这些进程/线程。IO多路复用使用两个系统调用(select/poll/epoll和recvfrom),blocking IO只调用了recvfrom;select/poll/epoll 核心是可以同时处理多个connection,而不是更快,所以连接数不高的话,性能不一定比多线程+阻塞IO好,多路复用模型中,每一个socket,设置为non-blocking,阻塞是被select这个函数block,而不是被socket阻塞的。
而我们从中摘取出我们所需要的关键的信息,比如说一个或一组线程处理多个TCP连接,而在当你说出这个多路复用的时候,你就会被面试官记下来,如果你能把多路复用这个IO模型讲清楚,那么你就对 Java 中的 NIO有所了解了,而关于这个 多路复用这块内容,阿粉之前的文章也有讲解
深度探测 java IO
在这篇文章中有你想要的答案呦。小编在这里就不在给大家讲解了,等你看完,绝对有所收获。而阿粉的同事就是因为这里,回答的并不好,给了面试官非常不好的感受,这才第几步就已经不行了?
其实说到这些的时候,一般的面试官很容易就让你停下来了,因为 IO 流虽然在我们日常操作里面经常的用,但是很多人接触的并不是特别的深刻,所以如果你在这里说的不太满意的话,那么面试官估计也就没有兴趣听下去了,但是阿粉还是要讲下去的。
异步 IO 模型
其实异步 IO 模型,这个烧水的案例解释的那叫一个清晰透彻,你把水壶放在了这个煤气灶上,然后你想起来,呀,还有代码没写完,然后你就直接去写代码了,而异步 IO 模型是最理想的 IO 模型,在异步 IO 模型中,当用户线程发起 read 操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个 asynchronous read 之后,它会立刻返回,说明 read 请求已经成功发起了,因此不会对用户线程产生任何block。
而内核会一直等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read 操作完成了。
只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接去使用数据了。
而需要注意的一点就是异步IO是需要类支持的Asynchronous,大家有兴趣的可以看这个文章,也是小编为大家提供的。
Java:前程似锦的 NIO 2.0
2.4面试官第四问
在 IO 这块的时候,小编的同事回答的如果说是百分之80的话,那么在这最后的第四问,就是属于压死骆驼的最后一根稻草了,
面试官:你对线程池的了解多不多,说一下你对线程池的了解吧,说实话,小编在听到有这个问题的时候,一点都不惊讶,毕竟大厂问得最多的就是多线程,线程问题一直是属于面试的大厂必问的知识点,而这个知识点就是属于看你对线程这块内容的掌握程度了。
线程池的组成
- 线程池管理器:用于创建并管理线程池
- 工作线程:线程池中的线程
- 任务接口:每个任务必须实现的接口,用于工作线程调度其运行
- 任务队列:用于存放待处理的任务,提供一种缓冲机制
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors,ExecutorService,ThreadPoolExecutor ,Callable 和 Future、FutureTask 这几个类
ThreadPoolExecutor
- corePoolSize:指定了线程池中的线程数量。
- maximumPoolSize:指定了线程池中的最大线程数量。
- keepAliveTime:当前线程池数量超过 corePoolSize 时,多余的空闲线程的存活时间,即多次时间内会被销毁。
- unit:keepAliveTime 的单位。
- workQueue:任务队列,被提交但尚未被执行的任务。
- threadFactory:线程工厂,用于创建线程,一般用默认的即可。
- handler:拒绝策略,当任务太多来不及处理,如何拒绝任务。
线程池原理:
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后,启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。他的主要特点为:线程复用;控制最大并发数;管理线程。
他再说完这些的时候,面试官感觉可能还行,然后继续深挖,而接下来的深挖,就彻底的被京东给拒之门外了,
面试官:你知道什么是拒绝策略么?在面试官的眼中可能会想,你上面吧线程池的组成,原理都说了,拒绝策略应该会吧,结果是我的同事真的没有了解这一块的内容,于是回答的是不太了解,而这个回答,面试官说那好吧,那我们今天就先聊到这里吧。
那么什么是拒绝策略?
拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略。也就是说,我的等待队列也满了,你给我说还有新的活要干,不好意思,我表示拒绝,而 JDK 自带的拒绝策略也是有好几个的,分别如下:
- AbortPolicy : 直接抛出异常,阻止系统正常运行。
- CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
- DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
- DiscardPolicy : 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。
以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍无法满足实际需要,完全可以自己扩展 RejectedExecutionHandler 接口。