同事被几个面试题,阻挡在了京东的门外

最近疫情稳定了,跳槽的换工作这块,又开始了,这不,阿粉公司的同事因为公司不涨工资的事情,已经开始踏上了重新面试,寻找更高级别的出路了,而前几天的面试,让他一次大好的机会被京东的线程面试题阻挡在了门外。

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 模型

来自百度

1
2
3
当用户线程发起一个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的话,那么在这最后的第四问,就是属于压死骆驼的最后一根稻草了,

面试官:你对线程池的了解多不多,说一下你对线程池的了解吧,说实话,阿粉在听到有这个问题的时候,一点都不惊讶,毕竟大厂问得最多的就是多线程,线程问题一直是属于面试的大厂必问的知识点,而这个知识点就是属于看你对线程这块内容的掌握程度了。

线程池的组成

  1. 线程池管理器:用于创建并管理线程池
  2. 工作线程:线程池中的线程
  3. 任务接口:每个任务必须实现的接口,用于工作线程调度其运行
  4. 任务队列:用于存放待处理的任务,提供一种缓冲机制

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 接口。

关于拒绝策略,阿粉会在之后的文章给大家在详细的讲讲这个里面到底是个什么东西,给大家进大厂做一些准备。

Java Geek Tech wechat
欢迎订阅 Java 极客技术,这里分享关于 Java 的一切。