面试实战第二期
介绍下我自己,我是王星星,2021届校招生,目前在阿里巴巴做Java开发。去年秋招的时候拿到了阿里,微信,拼多多,百度,美团,携程等互联网公司的offer。这个系列文章主要是通过总结牛客上的面经,自己梳理答案,然后分享出来,帮助广大实习和秋招同学稳拿offer,成功上岸!(本系列文章问题均得到牛客网友授权,更多面经答案请访问公众号:王星星的魔灯)
一面
一小时
-
select 和 epoll的区别
-
select和epoll都是同步非阻塞模型的不同形式,同步非阻塞模型可以参考:https://wxxlamp.cn/2021/04/29/my-university-leaning/
-
select和epoll的区别在于:select中需要将用户态的accept数组拷贝到内核态的数组中,同时返回的只是可读fd的个数,性能依旧不高,所以在epoll中,它只是传入的accept数组中修改的部分而不是全部,同时通过异步事件唤醒, 内核仅会将有 IO 事件的文件描述符返回给用户,epoll大大提高了性能。
-
-
NIO BIO AIO 分别谈谈
-
这个问题是上个问题的延伸,所谓的BIO,NIO,和AIO,在Java生态中,同步阻塞IO,1.4引入的非阻塞IO和1.7引入的异步IO
-
所谓的BIO,就是我们说的同步阻塞模型,当线程发起数据IO的时候,它会一直阻塞等待内核去读写数据,直至内核反馈结果后,该IO线程才会返回
-
对于NIO来说,当线程发起数据IO的时候,它会一直不断轮询内核是否将数据准备完成,直至内核反馈结果后,该IO线程才会去做其他工作。NIO延伸出来的有select,poll,epoll
-
对于AIO来说,当线程发起数据IO的时候,内核会立刻反馈给IO线程结果,然后IO线程就可以去做其他的事情,当内核真正在进程中准备好数据之后,会给IO线程发送IO成功的数据
-
-
自旋锁,CAS,有什么弊端 怎么解决
-
线程加锁的时间一般都很短,所以下一个需要获得锁的线程等一下在阻塞,这个等一下的过程就叫自旋。这属于synchronized的优化。自旋锁的缺点很明显,就是不确定需要自旋多长时间,如果自旋时间过长的话,会导致CPU飙高,影响其他线程执行。解决方式也很简单,就是所谓的适应性自旋锁,加一个自旋时间即可
-
CAS是指compare and set,如果说sync的阻塞是悲观锁的话,那么cas就是乐观锁了,通过版本号的比较,如果版本号相同,则说明没有并发问题,如果版本号不同,则说明有其他线程参与,不能修改。cas的弊端很明显,就是会产生aba问题,解决方法也很简单,通过版本号机制,保证数据只会出现一次
-
-
异步IO、同步IO,什么是异步、同步
-
所谓的同步IO,即是用户需要等到内核将设备(网络和硬盘)的数据全部拷贝到用户进程中之后才会给用户响应
-
而异步IO,则是用户发起请求后,内核就会给用户响应
-
-
实习做什么的,实习部门做什么的,遇到什么困难,怎么解决
-
这个是面试的经典问题,一般情况下要分一下几点:
-
介绍实习部门背景,以及自己的工作,以及工作的结果:技术上和业务上
-
在实习中遇到的困难:团队合作,代码熟悉,业务开发,技术bug等等
-
-
说设计模式使用的实际场景
-
我们常见的设计模式有:工厂方法模式,单例模式,代理模式,门面模式,模板方法模式,责任链模式,监听者模式,策略模式,装饰者模式等等
-
可以参考我这个博客:https://wxxlamp.cn/2021/07/19/mq-a-consumer-biz-resolution/
-
-
链表k个一组反转
class Solution { // 如果不是额外常数空间,可以用O(n)空间新建k个链表 // 这个题需要常量空间,所以只能用标记 public ListNode reverseKGroup(ListNode head, int k) { if (head == null || head.next == null) { return head; } ListNode tail = head; for (int i = 0; i < k; i++) { //剩余数量小于k的话,则不需要反转。 if (tail == null) { return head; } tail = tail.next; } // 反转前 k 个元素 ListNode newHead = reverse(head, tail); // 下一轮的开始的地方就是tail head.next = reverseKGroup(tail, k); return newHead; } /* 左闭又开区间 */ private ListNode reverse(ListNode head, ListNode tail) { ListNode pre = null, next = null; while (head != tail) { next = head.next; head.next = pre; pre = head; head = next; } return pre; } }
二面
45分钟,因为算法简单
-
AQS基本原理
-
对于AQS来说,他是一个用来构建锁和同步器的框架,基于CAS,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器
-
从使用上来说,AQS的功能可以分为两种:独占(如ReentrantLock)和共享(如Semaphore/CountDownLatch CyclicBarrier/ReadWriteLock)。ReentrantReadWriteLock可以看成是组合式,它对读共享,写独占
-
AQS维护一个共享资源state,它的语义有响应的子类来实现,譬如在ReentrantLock中,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。在CountDownLatch中,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作
-
当加锁时,子类通过调用AQS的acquire()进而调用子类自己实现的tryAcquire()方法(该方法是尝试获得锁,即改变state的状态),在调用tryAcquire()方法的过程中:先尝试获得锁,如果成功则返回,如果获得不成功则把该线程加入到Node队列中(节点为空时,自旋创建节点并设置尾点),然后自旋尝试获得锁(只有老二结点,才有机会去tryAcquire,如果不是且当其前驱节点是SINGAL,则阻塞),之后返回中断状态
-
当释放锁时,和加锁流程一样。当释放成功后则去唤醒后面的节点
-
-
什么是可重入锁,用源码解释一下
-
可重入锁即是说线程在获取锁A之后,调用到了另外一个需要锁A的方法,该线程是可以直接获得到该锁的
-
对于ReentrantLock来说,该锁通过state关键字来记录锁重入的次数
-
ReentrantLock和synchronized都是可重入锁
-
-
AQS的应用在哪里举例说明 怎么实现的
-
JUC中有线程池(Worker基于AQS),工具类(基于AQS的semaphore,countDownLock等),并发容器,atomic和locks(AQS,ReentrantLock),可以说,AQS是JUC的基础
-
参考第一题
-
-
举例jdk里面用过哪些设计模式
-
单例模式:java.lang.Runtime: 每个Java应用程序都有一个类运行时实例,该实例允许应用程序与运行应用程序的环境进行交互,很显然,只需要有一个Runtime实例即可,采用饿汉式
-
装饰者模式:JavaIO包中的InputStream和OutputStream
-
代理模式:基于JDK的动态代理
-
观察者模式:java.util.Observer
-
迭代器模式:集合中的Iterator
-
模板方法模式:AbstractList和JDBCTemplate
-
-
你怎么看云原生,什么是云原生,为什么想做云原生
-
所谓的云原生,是DevOps+持续交付+微服务+容器的集合
-
-
kubernetes和docker了解多少说多少
-
k8s是调度器,docker是被k8s调度的容器
-
-
netty做了什么优化 具体点
-
API优化,更利于使用,我们在编写代码的时候会有更适合业务的,更加清晰的语义
-
通过ChannelHandler可以对网络进行更细节层的定制,譬如说协议解析等等
-
性能方面,采用Reactor模型,具体可以参考这篇pdf:http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf
-
采用了不同于传统意义上的零拷贝:详情见第8题
-
-
零拷贝原理
-
netty的零拷贝除了节省内核缓冲区和用户缓冲区的拷贝次数,还在Java层做了一些处理
-
直接在内存区域分配空间,而不是在堆内存中分配。如果使用传统的堆内存分配,当我们需要将数据通过socket发送的时候,就需要从堆内存拷贝到直接内存,然后再由直接内存拷贝到网卡接口层。 Netty提供的DirectBuffer,直接将数据分配到内存空间,从而避免了数据的拷贝,实现了零拷贝。
-
Netty通过Composite Buffers保存了数据的引用,而不是将多个数据通过拷贝的形式组装成大对象,实现了零拷贝。
-
-
写一个生产者消费者
private static class Producer implements Runnable { @Override public void run() { try { empty.acquire(); mutex.acquire(); System.out.println("生产" + in); in = (in + 1) % BUFFER; } catch (InterruptedException e) { e.printStackTrace(); } finally { mutex.release(); full.release(); } } } private static class Consumer implements Runnable { @Override public void run() { try { full.acquire(); mutex.acquire(); System.out.println("消费" + out); out = (out + 1) % BUFFER; } catch (InterruptedException e) { e.printStackTrace(); } finally { mutex.release(); empty.release(); } } }
三面
1小时,估计是主管,人很好,但问的问题基本。。不知道。。。
-
什么是TCP连接 为什么TCP要连接
-
TCP协议是在运输层中有体现,它承接了网络层,同时开启了应用层。TCP通过端口号唯一标识一个连接
-
IP 层是不可靠的,它只负责数据包的发送,但它不保证数据包能够被接收、不保证网络包的按序交付、也不保证网络包中的数据的完整性。如果需要保障网络数据包的可靠性,那么就需要由上层(传输层)的 TCP 协议来负责。
-
-
socket编程中,三次链接怎么进行的,从哪里拿到连接的,连接成功的标志是什么
-
当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态
-
服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;
-
客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立
-
-
客户端挂了,那服务端能感知到TCP断了吗 不能怎么办,能的话是什么原理?
-
理论上是不行的,不过目前很多实现均是可以感知到的
-
socket库提供了SO_KEEPALIVE功能,能够通过定时发消息来检测客户端是否挂掉
-
也可以通过使用select()函数测试一个socket是否可读时,如果select()函数返回值为1,且使用recv()函数读取的数据长度为0 时,就说明该socket已经断开。
-
-
MVCC原理
-
数据库多版本并发控制,即每一行数据都是有多个版本的,每个版本有自己的row trx_id,即当时修改该行的transaction_id
-
需要用到一致性读视图,即consistent read view,用于支持RC和RR隔离级别的实现,它没有物理结构,作用是事务执行期间用来定义“我能看到什么数据”,它其实是一个视图数组,和数据库中显式创建的create view ...不一样
-
一个数据版本,对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:
-
版本未提交,不可见;
-
版本已提交,但是是在一致性视图创建后提交的,不可见;
-
版本已提交,而且是在一致性视图创建前提交的,可见
-
-
在MVCC中有两种读,上面三种是快照读,还有一种是当前读
-
当普通的select是快照读
-
插入,删除,更新属于当前读,需要加锁,遵从两阶段锁协议
-
-
-
分布式锁 业务服务器挂了 锁怎么释放?
-
设置锁的存活时间
-
-
除了innodb你还知道什么引擎
-
MyISAM:
-
MyISAM中是不会产生死锁的,因为MyISAM总是一次性获得所需的全部锁,要么全部满足,要么全部等待
-
MyISAM因为是表锁,只有读读之间是并发的,写写之间和读写之间是串行的
-
MyISAM的二级索引和主索引结构没有区别,但是二级索引的key可以不唯一
-
-
Memory:支持hash索引
-
-
平时怎么调试代码?怎么实现断点的?跑着的线程怎 么打断点让它停下来
-
IDEA的断点功能可以选择all和thread模式,如果选择thread模式的话,就可以针对线程打断点了
-
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode p1 = head, p2 = head; for(int i = 0; i < n; i++) { p1 = p1.next; } if(p1 == null) { return head.next; } while(p1.next != null){ p1 = p1.next; p2 = p2.next; } p2.next = p2.next.next; return head; } }