本文作者: 伯乐在线- 朱小厮。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。
本篇所要介绍的是一家互联网企业,简称MD好了。一面是电面,二三面是face2face的技术面,4面是HR面。
一面
一面的具体内容忘了记录了,大概面了45mins,问了30mins的NIO。
这里就来讲一讲NIO的一些知识点。NIO有三个部分:Selector, Channel和Buffer. 传统的IO基于字节流和字符流进行操作。而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区,或者从缓冲区写入通道中。Selector用于监听多个通道的时间(比如打开,数据到达),因此,单个线程可以监听多个数据通道。
Selector内部原理实际是在做一个对所注册的Channel的轮询访问,不断的轮询,一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会站起来报告,交出一把钥匙(SelectionKey),让我妈通过这把钥匙来读取channel的内容。
NIO有一个重要的点是:Reactor模式。
从网上抠来一张Reactor模式的模型图如下:
Reactor模式有两大角色:Reactor和Handler. Reactor负责响应I/O事件;Handler负责非阻塞行为。至于Reactor模式的编码风格我就不赘述了。
前面说到了Selector的原理就是轮询,但是Java NIO中有个epoll()(linux系统下),这个对并发idle Connection会有大幅度的性能提升。epoll是linux下多路复用I/O接口Select/poll的增强版本。一般的轮询方式一个进程打开的FD(File Deor)是有一定限制的,FD_SETSIZE默认是2048,这个宏定义可以修改,但修改后的效果并不理想,对于那些需要支持上万连接数的IM服务器来说显然太少。epoll没有这个限制,可以通过”cat /proc/sys/fs/file-max”来查看支持的FD上限。博主本地机器是205822。epoll()有两种使用模式:LT(Level triggered)缺省,ET(Edge-triggered)高速。
epoll有一个著名的epoll-bug, 也可能会导致无效的状态的选择和100%的CPU利用率,要解决epoll-bug的唯一方法是回收旧的选择,将先前注册的通道实例转移到新创建的选择器上。当然如果使用一些框架比如:Netty, Mina.
面的全问到了,问的还挺深的。最后问了为什么没用Netty之类的框架实现,还有有了解过其他的分布式框架么?
还问了一些Java基础题。
比如多线程的ThreadPollExecutor中的饱和策略有哪些?
Ans:当线程池和队列都满了,则表明该线程池已达饱和状态。
ThreadPoolExecutor.AbortPolicy:处理程序遭到拒绝,则直接抛出运行时异常 RejectedExecutionException。(默认策略)ThreadPoolExecutor.CallerRunsPolicy:调用者所在线程来运行该任务,此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。ThreadPoolExecutor.DiscardPolicy:无法执行的任务将被删除。ThreadPoolExecutor.DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重新尝试执行任务(如果再次失败,则重复此过程)。
线程池怎么合理配置?
Ans:需要针对具体情况而具体处理,不同的任务类别应采用不同规模的线程池,任务类别可划分为CPU密集型任务、IO密集型任务和混合型任务。
对于CPU密集型任务:线程池中线程个数应尽量少,不应大于CPU核心数;对于IO密集型任务:由于IO操作速度远低于CPU速度,那么在运行这类任务时,CPU绝大多数时间处于空闲状态,那么线程池可以配置尽量多些的线程,以提高CPU利用率;对于混合型任务:可以拆分为CPU密集型任务和IO密集型任务,当这两类任务执行时间相差无几时,通过拆分再执行的吞吐率高于串行执行的吞吐率,但若这两类任务执行时间有数据级的差距,那么没有拆分的意义。
线程数的个数?(这个问题二面也问到了)
Nthreads = Ncpu*Ucpu*(1+W/C)。Ncpu是cpu的个数,可以通过Runtime.getRuntime.availableProcessors()获取。Ucpu是cpu的使用率。W/C=Wait-Time/Compute-Time. ps:Runtime.getRuntime是单例模式。
二面
二面先对简历上的内容询问了一番,问了一些问题,再此就不表了,接下去问了些Java类的问题。
1.线程池ThreadPoolExecutor以及Executors的一些问题。
2.ReentrantLock和synchronized的区别。
ReentrantLock可以中断地获取锁(void lockInterruptibly() throws InterruptedException)ReentrantLock可以尝试非阻塞地获取锁(boolean tryLock())ReentrantLock可以超时获取锁。通过tryLock(timeout, unit),可以尝试获得锁,并且指定等待的时间。ReentrantLock可以实现公平锁。通过new ReentrantLock(true)实现。ReentrantLock对象可以同时绑定多个Condition对象,而在synchronized中,锁对象的的wait(), notify(), notifyAll()方法可以实现一个隐含条件,如果要和多于一个的条件关联的对象,就不得不额外地添加一个锁,而ReentrantLock则无需这样做,只需要多次调用newCondition()方法即可。
从上面的比较中可以看出ReentrantLock比synchronized有很多的优点,但是JDK的开发团队还是推崇使用synchronized的方式,在JDK5中ReentrantLock比synchronized的性能优越很多,在JDK6中进行了很多的优化,ReentrantLock比synchronized只是稍微好点,而且ReentrantLock用的不好,比如Lock.lock之后再finally块中忘了Lock.unlock操作锁就永远得不到释放,而synchronized可以自动释放锁。而且经过JDK的开发团队的不懈努力,synchronized的性能会越来越好,比如:偏向锁,轻量级锁,自旋锁,锁粗化以及锁细化等优化操作。
3.ReentrantLock又称之为什么?怎么实现的?还有那些相同的?
ReentrantLock又称之为可重入锁,内部实现是基于AQS的(对于AQS不明白的小伙伴可以度娘了)。还有一些也是基于AQS的比如Semaphore,还有ReentrantReadWriteLock(独占锁+共享锁)。在ReentrantReadWriteLock中又涉及了一个锁降级的概念:先Hold写锁,再获取读锁,再释放写锁。没有锁升级的概念。(有关多线程的知识,可以翻看博主相关的文章,详情见参考资料1-4。)
4.sleep和wait的区别?为什么sleep在Thread中,wait在Object中?
sleep不让出锁,它不会导致锁行为的改变,而wait会让出锁。Thread.sleep的调用不会影响锁的改变,所以放在sleep中,wait与锁有关所以放在Object中。
5.为什么Java中的锁是对象锁而不是线程锁?
这个问题比较抽象,主要是在对象锁的编程模型比较简单,线程锁其实也可以,但是编程模型太复杂,所以Java采用的对象锁。
6. 写一个单例模式。
博主写了一个内部类的单例模式,然后说了其他的写法比如:enum或者synchronized的写法,详情可以关注《设计模式:单例模式(Singleton)》。
后面又问了一个问题:一个类中所有的方法和属性都标注为static的,那么这是一个单例么?面试官的语气暗示这不是一个单例,但的确想不出不是单例的地方,想了一会儿才明白过来这的确是一个单例的写法。单例是保证一个类的实例构造器只实现一次,而全部标注为static的类,它就只有类构造器,类构造器可以由JVM确保只执行一次,而且是线程安全的。
还有一道设计题:微信公众号拉取粉丝数量。
三面
三面的面试官照着简历问了一些问题,然后问了一些其他的和技术无关的,最后还问了一个技术类问题:怎么可以发生Full GC?
Full GC是指老年代发生了Stop the world的操作。详细可以参考《Java堆内存 》。
比如在CMS执行时有初始标记、并发标记、重新标记、并发清除、并发重置5个步骤中,初始标记和重新标记都是stop the world的,当发生Concurrent Mode Failure时,会触发一次Serial-Old的操作。
可以在程序中通过设置-Xmx, -Xms等参数去触发,也可以通过System.gc()触发。当然有两个工具JConsole和JVsualVm中有“GC”的按钮。
(剩下的可以在留言区补充。)
总结:无。(对这家公司太熟悉,不方便评价~~)
更多链接请关注:
这里有一份面经,请查收(一)
这里有一份面经,请查收(二)
这里有一份面经,请查收(三)
这里有一份面经,请查收(四)
这里有一份面经,请查收(五)
这里有一份面经,请查收(六)
打赏支持我写出更多好文章,谢谢!
打赏作者 打赏支持我写出更多好文章,谢谢!
任选一种支付方式
1赞收藏评论
关于作者:朱小厮
Java相关技术爱好者 个人主页· 我的文章· 8·