近期好文:
秋招还没offer怎么办?面试指南 | 重磅来袭!!
阿里巴巴 CTO 线 Java一面,稳了 (50min)
百度一面,顶不住
得物一面,场景题问得有点多
MySQL索引结构,为何选用B+树,悟了
最近某客上一篇面经又炸啦,评论区网友直呼顶不住,今天主要分享这位网友的面经
面经原帖地址:https://www.nowcoder.com/discuss/542181239265013760
面经概览
一面
1.自我介绍一下?
2.你 Java是怎么学的?学校有讲吗?
3.进程的通信方式有哪些?
4.给你几个看看进程通信方式和几种场景,你看看它们是不是对应?
- 消息队列:redis的pub和sub
- 信号:linux的kill命令
- 管道:ps xx | grep xx
- 共享内存:MySQL的buffer pool
(这几个都是一一对应的,面试官跟我解释说这是一种抽象,不是具体编码实现,是那个思想)
5.网络五层协议是哪五层?上面三层常见的协议服务器托管网可以说一下吗?
6.网络层除了IP,还有哪些协议?
7.linux中,哪些命令与应用层的协议相关?哪些命令与传输层的协议相关?哪些命令与网络层的协议相关?你可以举几个例子吗?
候选人说:面试官在聊天框又写了 netstat,ss,lsof 这些命令,然后跟我讲解这些命令,包括还提到了 IP 等命令
8.Map map = new HashMap(100)
,往里面 put 多少个元素会扩容?
候选人说:这里跟他说这不好说,因为hash冲突也会引起扩容,他跟我说不考虑冲突;后来面完想了想,因为容量已经大于64了,hash冲突就不会引起扩容了
9.为什么是128*0.75?
10.为什么它要把长度调整到 2的n次幂 ?
11.ReentrantLock 的 lock 做了什么事情?概括一下?先考虑只有一个线程的情况?
12.只做了cas吗?
13.你可能漏了些什么;那我这个线程再次执行这个lock方法会怎么样?
14.那如果换一个线程过来执行这个lock呢?
15.那为啥原来的那个线程可以重入,新线程不能重入?
16.AQS有个当前线程的变量吗?怎么标记当前线程是否自己呀?
17.Java里面把线程挂起的方式有哪些?
面试官提示:sleep 和 join 不算吗?(明牌提示)
18.这里面有个 wait
方法比较特殊,你知道 wait
方法是要在什么条件下才能用吗?
19.为什么要在同步代码块里面才能用呢?有思考过什么原因吗?
候选人说:他说我说的没毛病,但是有点不完整,稍微完善一点的是这个意思,sync是个关键字,在编译的时候会编译成monitorenter和moniterexit指令,会生成一个辅助对象,也就是ObjectMonitor,来辅助sync的执行;
这个对象里面有个核心的类似aqs的结构体,也就是我说的waiter队列和state和owner,wait必须依靠这个waiter队列进行挂起,所以说它必须依赖ObjectMonitor这个对象,如果没有这个对象,那就会报错
20.NIO核心的三大组件是什么?
候选人说:我在说 selector 的时候说了个 epoll,他跟我说不一定是 epoll,还可能是 kqueue,甚至是 select 和 poll,要看具体的操作系统以及操作系统的版本
21.你在网络传输的时候,传输的字节,会出现黏包半包问题,你知道黏包半包问题有什么解决思路吗?
22.你在哪些中间件或者哪些地方见过黏包半包的这些解决思路吗?
23.除了netty还有吗?
候选人说:然后他说我刚才说的基于长度字段方式,让他想起了 kafka
的协议,然后讲了 kafka
里面的黏包半包问题
24.问下实习吧,你在实习的公司做过什么项目?
25.你觉得你的系统的用户量是百级千级还是万级呢?
26.你们项目部署的话,是部署多少台机器呢?
27.你可以说一下你做的一些的核心功能吗?
28.你觉得你这个预约系统的接口里面,最核心的接口是哪个接口?
29.你说你还有下游,你说说你跟下游通信的方式?Feign 还是 HttpClient?
30.我看你之前做过rpc,你应该很清楚模块与模块之间调用的方式吧?
我给你举个场景,我本地服务A,有个C接口,要调用M方法,这个M方法是在服务B上的,但是我现在服务A上面只有C这个接口,而且是没有任何实现类的,那它是怎么实现调用服务B的呢?
你是不是还漏了什么?服务A是直接跟服务B发起请求的吗?前面没有其他步骤了吗?
候选人说:然后他开始讲起来他对 rpc 实现的思路,从注册中心,协议,序列化,连接方式,代理模式等等;跟我说这个框架不难写,但如果要实现的像 dubbo 和 grpc 那样,还是要有点水平
然后就结束了,他说我是本科,看得出来我很热爱技术,对技术有自己的理解,在本科很少见,但是技术涉及的不是很多,也比较简单和基本;最后说要给我个建议,讲了十分钟。最后没有反问
可以看出来面试官是个真大佬,对很多东西都很熟悉,面试的时候边打哈欠边面,一边吃面包一边喝水,很自然但是很凌厉,一点不拘谨,随手在留言板上面写两句代码都能问很多东西,能从一个问题扩展到其他问题,也给了很多提示,补齐我说漏的东西;特别是最后赶时间还是跟我讲了好久的建议,是真的很感谢
二面
1.自我介绍一下?
2.没有考虑过考研吗?考研和工作两方面是怎么考虑的?你的专业课有哪些?(作为双非本被问这些已经不惊讶了)
3.操作系统的进程和线程有什么样的联系和区别?
4.Java里面也有进程和线程,Java里面的进程和线程跟操作系统的进程和线程有什么区别?
5.我在一个jvm进程里面,最多能开多少个线程,原因是什么?
6.一个线程的创建需要消耗哪些资源呢?
7.http熟悉吗?1.0,1.1,2.0有什么区别?
8.3.0也出来啦,3.0解决了什么问题呢?
9.一个表有学生号,学生名,学科,学期,成绩;查出每个学期的每个学科的最高分学生名(没写出来。。。)
10.聊聊你毕业后的计划吧?
**候选人说:**二面也是没有反问,二面面试官非常和蔼可亲,很温柔,讲话也有点俏皮,SQL 写不出来都给我过了
但是也狠狠地批评我,说我有个问题至少犯了四次,还没看清题目,没听完要求就开始写了,他要我直接在代码里面初始化数组就好了,我还用了scanner,甚至那个算法题的题目都是有二义性的,我也没发现,也没问清楚,关键还是在于审题,而不是二分法;
听得我很慌,以为要挂了。然后告诉我以后工作也是,写代码也是,都要先听需求,先思考,写代码的时候永远是最短的
最后说没关系,说我面试能学到东西就好,其他的问题跟hr聊吧
HR面
常规HR面,介绍实习,实习收获,为什么不转正,毕业志向,个人优缺点
反问,11月左右反馈面试结果
没有提前实习的要求
建议
面经详解
一面
自我介绍一下
略
你Java是怎么学的?学校有讲吗?
略
进程的通信方式有哪些?
进程间通信(Inter-Process Communication,IPC)的方式有以下几种:
- 管道(Pipe):管道是一种半双工的通信方式,可以在具有亲缘关系的进程之间进行通信。它可以是匿名管道(只能在父子进程间使用)或有名管道(可以在任意进程间使用)。
- 信号(Signal):信号是一种异步通信方式,用于通知进程发生了某个事件。进程可以通过注册信号处理函数来捕获和处理信号。
- 消息队列(Message Queue):消息队列是一种通过消息传递进行通信的方式,可以在不同进程之间传递数据。每个消息都有一个类型和一个数据部分。
- 共享内存(Shared Memory):共享内存是一种高效的通信方式,多个进程可以将同一块内存映射到自己的地址空间,实现数据的共享。
- 信号量(Semaphore):信号量是一种用于进程间同步和互斥的机制。它可以用来保护共享资源,避免多个进程同时访问造成的数据不一致问题。
- 套接字(Socket):套接字是一种网络编程中常用的通信方式,可以在不同主机上的进程之间进行通信。
以上是常见的进程间通信方式,每种方式都有其适用的场景和特点。在面试中,我可以进一步详细介绍每种方式的原理、优缺点以及适用场景。
给你几个看看进程通信方式和几种场景,你看看它们是不是对应?
- 消息队列:redis的pub和sub
- 信号:linux的kill命令
- 管道:ps xx | grep xx
- 共享内存:MySQL的buffer pool
面试回答:是的,您提到的几个场景和对应的进程通信方式是相匹配的。
-
消息队列:Redis 的
pub/sub
机制使用了消息队列的方式进行进程间通信。发布者(pub)将消息发布到特定的频道,订阅者(sub)可以通过订阅该频道来接收消息。 - 信号:Linux 的
kill
命令可以向指定进程发送信号,用于通知进程发生了某个事件。通过发送不同的信号,可以实现进程的终止、暂停、继续等操作。 - 管道:在命令行中,使用管道符(|)可以将一个命令的输出作为另一个命令的输入。例如,ps xx | grep xx 将ps命令的输出通过管道传递给grep命令进行过滤,实现了进程间的通信。
-
共享内存:MySQL 的
buffer pool
是一块共享内存区域,用于缓存数据库中的数据页。多个MySQL进程可以将该共享内存映射到各自的地址空间,实现数据的共享和高效访问。
这些场景和对应的进程通信方式都是合理的,它们在实际应用中发挥了重要的作用。
网络五层协议是哪五层?上面三层常见的协议可以说一下吗?
网络五层协议是指OSI(Open Systems Interconnection)参考模型中的五层协议,包括以下五层:
- 物理层(Physical Layer):负责传输比特流,将数据转换为电信号进行传输。常见的物理层协议有以太网(Ethernet)、Wi-Fi、光纤等。
- 数据链路层(Data Link Layer):负责在物理连接上传输数据帧,并提供可靠的数据传输。常见的数据链路层协议有以太网协议(Ethernet)、PPP(Point-to-Point Protocol)等。
- 网络层(Network Layer):负责将数据包从源主机传输到目标主机,实现网络间的路由和转发。常见的网络层协议有IP(Internet Protocol)、ICMP(Internet Control Message Protocol)等。
常见的三层协议如下:
- IP(Internet Protocol):是网络层的核心协议,负责将数据包从源主机传输到目标主机。它使用IP地址来标识主机和网络,实现了跨网络的数据传输。
- TCP(Transmission Control Protocol):是传输层的协议之一,提供可靠的、面向连接的数据传输。TCP通过序号、确认和重传机制来保证数据的可靠性。
- UDP(User Datagram Protocol):也是传输层的协议之一,提供不可靠的、无连接的数据传输。与TCP不同,UDP不保证数据的可靠性,但传输效率较高,适用于实时性要求较高的应用场景。
这些协议在网络通信中起着重要的作用,每个层级都有不同的功能和责任,共同构成了网络通信的基础。
网络层除了IP,还有哪些协议?
除了IP协议,网络层还涉及到以下几个常见的协议:
- ICMP(Internet Control Message Protocol):ICMP是网络层的一个子协议,用于在IP网络中传递控制消息。它主要用于网络故障排除、错误报告和网络管理。
- ARP(Address Resolution Protocol):ARP是用于将IP地址解析为物理地址(MAC地址)的协议。它在局域网中工作,通过广播请求来查找目标IP地址对应的MAC地址。
- RARP(Reverse Address Resolution Protocol):服务器托管网RARP是与ARP相反的协议,用于将物理地址(MAC地址)解析为IP地址。它允许主机在没有配置IP地址的情况下通过物理地址进行网络引导。
- IPsec(Internet Protocol Security):IPsec是一组协议和算法,用于提供网络层的安全性和数据完整性。它可以用于加密和认证IP数据包,保护网络通信的机密性和安全性。
- OSPF(Open Shortest Path First):OSPF是一种用于动态路由选择的内部网关协议(IGP)。它通过计算最短路径来选择最优的路由,实现网络中的数据包转发。
这些协议在网络层起着重要的作用,用于实现网络间的路由、错误控制、安全性等功能。
linux中,哪些命令与应用层的协议相关?哪些命令与传输层的协议相关?哪些命令与网络层的协议相关?你可以举几个例子吗?
在Linux中,以下是一些与应用层、传输层和网络层协议相关的命令:
与应用层协议相关的命令:
- telnet:用于通过Telnet协议远程登录到远程主机。
- ftp:用于通过FTP协议进行文件传输。
- ssh:用于通过SSH协议进行远程登录和安全文件传输。
- httpie/curl:用于通过HTTP协议与Web服务器进行交互,发送HTTP请求并获取响应。
与传输层协议相关的命令:
- netstat:用于显示网络连接、路由表和网络接口等信息,可以查看TCP和UDP连接状态。
- nc:用于创建任意类型的网络连接,可以用于测试TCP/UDP端口的连通性。
- iptables:用于配置Linux内核的防火墙规则,可以基于TCP/UDP端口进行过滤和转发。
与网络层协议相关的命令:
- ping:用于测试网络连接的连通性,基于ICMP协议发送回显请求并接收回显应答。
- traceroute/mtr:用于跟踪数据包在网络中的路径,显示数据包经过的路由器和延迟。
- route:用于查看和配置路由表,控制数据包的转发。
这些命令与不同层级的协议相关,可以帮助我们在Linux系统中进行网络调试、连接测试和配置管理等操作。
面经作者说:(面试官在聊天框又写了netstat,ss,lsof这些命令,然后跟我讲解这些命令,包括还提到了ip等命令)
Map map=new HashMap(100),往里面put多少个元素会扩容?
在Java中,HashMap的扩容是基于负载因子(load factor)和初始容量(initial capacity)来确定的。负载因子是指HashMap在达到容量的百分比时进行扩容,默认为 0.75。
在给定初始容量的情况下,当HashMap中的元素数量超过负载因子乘以初始容量时,就会触发扩容操作。具体来说,在给定初始容量为100的 HashMap 中,当元素数量超过 75 个时,就会进行扩容。
需要注意的是,HashMap的扩容是一个相对耗时的操作,因为它需要重新计算哈希值、重新分配存储空间,并将原有的元素重新散列到新的存储位置。因此,在使用HashMap时,尽量提前估计元素数量,并设置合适的初始容量,以减少扩容操作的频率。
面经作者说:(这里跟他说这不好说,因为hash冲突也会引起扩容,他跟我说不考虑冲突;后来面完想了想,因为容量已经大于64了,hash冲突就不会引起扩容了)
为什么它要把长度调整到2的n次幂?
HashMap在进行扩容时,会将容量调整为2的幂次方。这是因为 HashMap
使用的散列算法中,计算元素在数组中存储位置的方法是通过对元素的哈希值进行位运算。具体来说,HashMap会使用 (n – 1) & hash 的方式来计算元素在数组中的索引位置,其中 n 是数组的长度,hash 是元素的哈希值。
通过使用位运算 (n - 1) & hash
,可以保证元素的哈希值在数组中的存储位置均匀分布,减少哈希冲突的概率。而当数组的长度是2的幂次方时,(n – 1) 的二进制表示中所有位都是1,这样与任何哈希值进行位运算都能保证结果在数组索引范围内。这种方式可以提高散列算法的效率和均匀性。
另外,调整容量为 2 的幂次方还有一个好处是,在进行哈希值与数组长度进行位运算时,可以使用位运算符 & 代替取模运算符 %,位运算的效率更高。
因此,HashMap将容量调整为2的幂次方是为了提高散列算法的效率和均匀性,同时也能够使用位运算来代替取模运算,提高性能。
当HashMap进行扩容时,会将容量调整为2的幂次方,主要有以下几个原因:
- 提高散列性:HashMap使用散列算法将键映射到桶(数组的索引)上。当容量为2的幂次方时,通过与运算(hash & (capacity – 1))可以实现快速计算桶的索引,因为2的幂次方的二进制表示只有一个1,减1后的二进制表示全部为1,这样与运算可以保证散列均匀分布在桶中。
- 减少碰撞:碰撞是指不同的键映射到了同一个桶上。当容量为2的幂次方时,通过与运算可以保证散列均匀分布在桶中,减少了碰撞的概率,提高了HashMap的性能。
- 提高扩容效率:HashMap在扩容时,需要将原有的键值对重新分配到新的桶中。当容量为2的幂次方时,通过与运算可以快速计算出键值对在新桶中的位置,减少了重新分配的时间和成本。
- 优化计算效率:计算机中的位运算(如与运算)比乘除运算效率更高。通过将容量调整为2的幂次方,可以使用位运算来代替乘除运算,提高了HashMap的计算效率。
综上所述,将HashMap的容量调整为2的幂次方可以提高散列性、减少碰撞、提高扩容效率和优化计算效率,从而提升HashMap的性能和效率。
ReentrantLock的lock做了什么事情?概括一下?先考虑只有一个线程的情况
ReentrantLock 是 Java 中的一个可重入锁,它提供了与 synchronized 关键字类似的功能,用于实现线程之间的互斥访问。
当只有一个线程调用 ReentrantLock 的 lock 方法时,它会尝试获取锁。如果锁当前没有被其他线程持有,那么该线程会成功获取锁,并继续执行后续的代码。同时,ReentrantLock会记录当前持有锁的线程和获取锁的次数。
在这种情况下,如果同一个线程再次调用lock方法,它会增加获取锁的次数,而不会被阻塞。这种机制称为可重入性,即同一个线程可以多次获取同一个锁,而不会产生死锁。
当线程执行完需要保护的代码块后,可以调用 ReentrantLock 的 unlock 方法释放锁。在释放锁的过程中,ReentrantLock会判断当前线程是否还持有锁,如果是,则减少获取锁的次数。只有当获取锁的次数减少到0时,锁才会完全释放,其他等待获取锁的线程才有机会获取到锁。
总结起来,ReentrantLock的lock方法在只有一个线程的情况下,会尝试获取锁并记录获取锁的次数,而unlock方法会释放锁并减少获取锁的次数,实现了可重入性。
那我这个线程再次执行这个lock方法会怎么样?
当同一个线程再次执行 ReentrantLock
的 lock 方法时,会增加获取锁的次数。这种机制称为可重入性,即同一个线程可以多次获取同一个锁,而不会产生死锁。
具体来说,如果当前线程已经持有了锁,再次调用lock方法时,锁的持有计数会增加。这意味着线程在释放锁之前,可以多次获取锁,而不会被阻塞。
当线程执行完需要保护的代码块后,可以调用 ReentrantLock 的 unlock 方法释放锁。在释放锁的过程中,ReentrantLock 会判断当前线程是否还持有锁,如果是,则减少获取锁的次数。只有当获取锁的次数减少到0时,锁才会完全释放,其他等待获取锁的线程才有机会获取到锁。
总结起来,ReentrantLock 的 lock 方法在同一个线程多次执行时,会增加获取锁的次数,而 unlock 方法会释放锁并减少获取锁的次数,实现了可重入性。这种机制可以避免同一个线程因为重复获取锁而产生死锁,并且提供了更灵活的锁控制。
那如果换一个线程过来执行这个lock呢?
当另一个线程尝试执行ReentrantLock的lock方法时,如果锁已经被其他线程持有,情况如下:
- 锁未被其他线程持有:
- 尝试获取锁的线程会成功获取锁,并继续执行后续的代码。
- 锁已被其他线程持有:
- 尝试获取锁的线程会被阻塞,进入等待状态,直到锁被释放。
- ReentrantLock根据锁的类型(公平锁或非公平锁)决定下一个获取锁的线程是哪个。
- 公平锁:按照线程的请求顺序来分配锁,即先到先得。
- 非公平锁:允许新来的线程插队,有可能导致已经在等待的线程长时间等待。
- 持有锁的线程释放锁:
- 持有锁的线程执行完需要保护的代码块后,调用ReentrantLock的unlock方法释放锁。
- 在释放锁的过程中,ReentrantLock会唤醒等待队列中的一个线程,使其有机会获取到锁并继续执行。
总结起来,当另一个线程尝试执行ReentrantLock的lock方法时,如果锁已经被其他线程持有,尝试获取锁的线程会被阻塞,直到锁被释放。具体的等待和唤醒策略取决于锁的类型(公平锁或非公平锁)。
Java里面把线程挂起的方式有哪些?
在Java中,有几种方式可以将线程挂起:
-
Thread.sleep(long millis)
:这个方法会使当前线程暂停执行指定的时间(以毫秒为单位)。线程在休眠期间不会占用CPU资源,可以用于实现简单的定时等待。 -
Object.wait()
:这个方法会使当前线程进入等待状态,直到其他线程调用相同对象的notify()或notifyAll()方法来唤醒它。wait()方法必须在同步代码块或同步方法中调用。 -
Thread.join()
:这个方法会使当前线程等待调用它的线程执行完毕。调用join()方法的线程会被挂起,直到目标线程执行完毕才会继续执行。 -
LockSupport.park()
:这个方法可以使当前线程挂起,类似于Thread.sleep(),但是它没有指定时间参数。它需要与LockSupport.unpark()方法配合使用,unpark()方法可以唤醒指定线程。
但线程挂起并不意味着线程的终止,只是暂时停止了线程的执行,等待某个条件满足或其他线程的唤醒。
这里面有个wait方法比较特殊,你知道wait方法是要在什么条件下才能用吗?
是的,wait()方法需要在满足以下条件时才能使用:
- wait()方法必须在同步代码块或同步方法中调用:wait()方法依赖于对象的监视器(monitor),因此必须在获取对象的锁之后才能调用wait()方法。这意味着wait()方法必须在synchronized关键字修饰的代码块或方法中使用。
- wait()方法必须在循环中使用:当线程被唤醒后,它会重新进入就绪状态,但不一定立即获得锁。因此,在调用wait()方法后,应该在一个循环中检查条件,以防止虚假唤醒(spurious wakeup)。
- wait()方法必须与notify()或notifyAll()方法配合使用:wait()方法会使当前线程进入等待状态,直到其他线程调用相同对象的notify()或notifyAll()方法来唤醒它。因此,在使用wait()方法时,必须确保其他线程能够在适当的时候调用notify()或notifyAll()方法。
这些条件的目的是确保wait()方法的正确使用,以避免死锁、竞态条件等问题。同时,这也是Java中实现线程间协作的一种常见方式。
为什么要在同步代码块里面才能用呢?有思考过什么原因吗?
我的理解:
在 Java 中,wait() 方法必须在同步代码块或同步方法中调用的原因是与对象的监视器(monitor)相关。
每个Java对象都有一个与之关联的监视器,也称为内置锁(intrinsic lock)或互斥锁(mutex lock)。同步代码块或同步方法使用这个锁来实现线程的同步和互斥访问。
当一个线程进入同步代码块时,它会尝试获取对象的锁。如果锁已经被其他线程持有,那么当前线程将被阻塞,直到锁被释放。这样可以确保同一时间只有一个线程能够执行同步代码块中的代码,避免了多个线程同时访问共享资源导致的数据不一致或竞态条件问题。
wait()方法需要在同步代码块或同步方法中调用的原因是,它依赖于对象的监视器(锁)。调用wait()方法会释放当前线程持有的锁,并使线程进入等待状态,直到其他线程调用相同对象的 notify()
或 notifyAll()
方法来唤醒它。
如果 wait() 方法不在同步代码块或同步方法中调用,那么当前线程就没有持有对象的锁,也就无法释放锁,也就无法让其他线程获得锁并唤醒等待的线程。这样就无法实现线程间的协作和同步。
因此,为了正确使用wait()方法,必须在同步代码块或同步方法中调用,以确保线程在调用wait()方法时持有对象的锁,并且能够在适当的时候被其他线程唤醒。
博主说:(他说我说的没毛病,但是有点不完整,稍微完善一点的是这个意思,sync是个关键字,在编译的时候会编译成monitorenter和moniterexit指令,会生成一个辅助对象,也就是ObjectMonitor,来辅助sync的执行,这个对象里面有个核心的类似aqs的结构体,也就是我说的waiter队列和state和owner,wait必须依靠这个waiter队列进行挂起,所以说它必须依赖ObjectMonitor这个对象,如果没有这个对象,那就会报错)
NIO核心的三大组件是什么?
NIO(New I/O)是Java中提供的一种基于通道和缓冲区的I/O操作方式,相比传统的流式I/O,NIO具有更高的效率和灵活性。
NIO的核心组件包括:
- 通道(Channel):通道是NIO中用于数据传输的对象,类似于传统I/O中的流。通道可以是双向的,可以从通道读取数据,也可以向通道写入数据。通道可以与文件、网络套接字等进行连接,并通过缓冲区进行数据的读写操作。
- 缓冲区(Buffer):缓冲区是NIO中用于存储数据的对象,类似于传统I/O中的字节数组。缓冲区提供了一种方便的方式来读写数据,可以将数据从通道读取到缓冲区中,或者将数据从缓冲区写入到通道中。缓冲区可以分为直接缓冲区和非直接缓冲区,直接缓冲区使用操作系统的内存,读写效率更高。
- 选择器(Selector):选择器是NIO中用于多路复用的对象,可以通过一个线程来管理多个通道的读写操作。选择器可以检测多个通道上的事件,如连接就绪、读就绪、写就绪等,并通过选择器的选择操作来进行处理。使用选择器可以大大减少线程的数量,提高系统的并发性能。
这三个核心组件共同构成了 NIO 的基础架构,通过它们可以实现高效的非阻塞 I/O 操作,适用于处理大量的并发连接和高负载的网络应用。
面经作者说:(我在说selector的时候说了个 epoll,他跟我说不一定是 epoll,还可能是kqueue,甚至是select和poll,要看具体的操作系统以及操作系统的版本)
你在网络传输的时候,传输的字节,会出现黏包半包问题,你知道黏包半包问题有什么解决思路吗?
黏包和半包问题是在网络传输中常见的问题,主要是由于数据的传输和接收速度不一致导致的。解决黏包和半包问题可以采取以下几种思路:
- 定长消息:发送方在发送数据时,将每个消息固定长度,接收方按照固定长度进行接收和解析。这种方式简单直接,但是会造成消息长度固定,浪费带宽的问题。
- 分隔符:发送方在每个消息的末尾添加一个特定的分隔符,接收方根据分隔符将接收到的数据进行拆分。这种方式需要保证分隔符不会出现在消息内容中,否则会出现解析错误的问题。
- 消息头+消息体:发送方在每个消息前添加一个固定长度的消息头,消息头中包含消息的长度信息,接收方先读取消息头,根据消息长度读取对应长度的消息体。这种方式可以解决黏包和半包问题,并且可以支持不同长度的消息。
- 使用专门的协议:可以使用一些专门的协议来解决黏包和半包问题,如TCP粘包协议、自定义协议等。这些协议会在数据传输中添加一些额外的信息,用于标识消息的开始和结束,从而解决黏包和半包问题。
- 使用消息队列:可以将接收到的数据先放入消息队列中,然后再从消息队列中按照消息的完整性进行读取和处理。这种方式可以解耦数据的接收和处理,提高系统的可靠性和可维护性。
以上是一些常见的解决思路,具体的选择可以根据实际情况和需求进行考虑。在实际应用中,可以根据具体的业务需求和网络环境选择合适的解决方案。
你在哪些中间件或者哪些地方见过黏包半包的这些解决思路吗?
常见的中间件和框架,它们在处理黏包和半包问题时使用了相应的解决思路:
- Netty:Netty是一个高性能的网络应用框架,它提供了自定义的编码器和解码器,可以根据具体的协议和需求来处理黏包和半包问题。通过设置合适的分隔符或者消息长度,可以将接收到的数据拆分为完整的消息。
- Apache MINA:Apache MINA是一个基于Java的网络应用框架,它提供了类似于Netty的解决方案。通过使用自定义的编解码器,可以将接收到的数据按照特定的分隔符或者消息长度进行拆分和解析。
- RabbitMQ:RabbitMQ是一个开源的消息队列中间件,它使用AMQP协议进行消息传输。在消息的传输过程中,RabbitMQ会根据消息的完整性进行拆分和处理,从而解决黏包和半包问题。
- Kafka:Kafka是一个分布式流处理平台,它使用消息队列的方式进行数据传输。Kafka通过使用消息头和消息体的方式来解决黏包和半包问题,接收方可以根据消息头中的长度信息来读取对应长度的消息体。
问下实习吧,你在实习的公司做过什么项目?
回答略
- 你觉得你的系统的用户量是百级千级还是万级呢?
- 你们项目部署的话,是部署多少台机器呢?
- 你可以说一下你做的一些的核心功能吗?
- 你觉得你这个预约系统的接口里面,最核心的接口是哪个接口?
- 那你就说一下吧,你这个接口从前端发起请求,直到返回给前端,一共经过了哪些组件、中间件和系统?分别做了什么事?
- 你说你还有下游,你说说你跟下游通信的方式?Feign还是HttpClient?
- 我看你之前做过rpc,你应该很清楚模块与模块之间调用的方式吧?我给你举个场景,我本地服务A,有个C接口,要调用M方法,这个M方法是在服务B上的,但是我现在服务A上面只有C这个接口,而且是没有任何实现类的,那它是怎么实现调用服务B的呢?
- 你是不是还漏了什么?服务A是直接跟服务B发起请求的吗?前面没有其他步骤了吗?
(然后他开始讲起来他对rpc实现的思路,从注册中心,协议,序列化,连接方式,代理模式等等;跟我说这个框架不难写,但如果要实现的像dubbo和grpc那样,还是要有点水平)
面经作者感受
然后就结束了,他说我是本科,看得出来我很热爱技术,对技术有自己的理解,在本科很少见,但是技术涉及的不是很多,也比较简单和基本;最后说要给我个建议,讲了十分钟。最后没有反问
可以看出来面试官是个真大佬,对很多东西都很熟悉,面试的时候边打哈欠边面,一边吃面包一边喝水,很自然但是很凌厉,一点不拘谨,随手在留言板上面写两句代码都能问很多东西,能从一个问题扩展到其他问题,也给了很多提示,补齐我说漏的东西;特别是最后赶时间还是跟我讲了好久的建议,是真的很感谢
二面
自我介绍一下
略
没有考虑过考研吗?考研和工作两方面是怎么考虑的?你的专业课有哪些?(作为双非本被问这些已经不惊讶了)
略
操作系统的进程和线程有什么样的联系和区别?
操作系统的进程和线程是操作系统中的两个重要概念,它们之间有联系,同时也有区别。
联系:
- 进程和线程都是操作系统进行任务调度和资源分配的基本单位。
- 进程和线程都可以执行代码,具有独立的执行流程和程序计数器。
- 进程和线程都可以拥有自己的堆栈空间和局部变量。
- 进程和线程都可以与其他进程或线程进行通信和同步。
区别:
- 调度和资源分配:进程是操作系统进行资源分配和调度的基本单位,拥有独立的地址空间和系统资源。而线程是进程内的执行单元,共享进程的地址空间和系统资源。
- 资源占用:进程拥有独立的内存空间和系统资源,包括文件描述符、打开的文件等。而线程共享进程的资源,只拥有独立的栈空间和寄存器状态。
- 创建和销毁:创建和销毁进程需要较大的开销,包括分配内存空间、建立上下文等。而线程的创建和销毁开销较小,可以在进程内动态创建和销毁。
- 并发性:不同进程之间是并发执行的,而同一进程内的多个线程是并行执行的,可以充分利用多核处理器的性能。
总结起来,进程是资源分配和调度的基本单位,拥有独立的地址空间和系统资源;线程是进程内的执行单元,共享进程的资源,可以实现并行执行。进程和线程在操作系统中相互配合,共同完成任务的执行和资源的管理。
Java里面也有进程和线程,Java里面的进程和线程跟操作系统的进程和线程有什么区别?
在Java中,进程和线程的概念与操作系统的进程和线程有一些区别,主要体现在以下几个方面:
- 创建和销毁的开销:在操作系统中,创建和销毁进程的开销较大,需要分配独立的内存空间和建立上下文等。而在Java中,创建和销毁线程的开销相对较小,可以通过Java虚拟机(JVM)的线程管理机制来完成。
- 资源的管理:在操作系统中,进程拥有独立的地址空间和系统资源,包括文件描述符、打开的文件等。而在Java中,线程是在进程内部的执行单元,共享进程的资源,包括堆内存、静态变量等。Java提供了线程同步和互斥的机制,以保证线程之间的安全访问共享资源。
- 并发性的实现:在操作系统中,不同进程之间是并发执行的,而同一进程内的多个线程是并行执行的,可以充分利用多核处理器的性能。在Java中,线程是并发执行的基本单位,通过Java虚拟机的线程调度器来实现线程的并发执行。
- 跨平台性:Java是一种跨平台的编程语言,可以在不同的操作系统上运行。因此,Java中的进程和线程的实现是基于Java虚拟机的,与具体的操作系统无关。Java提供了一套统一的线程管理机制,使得开发者可以在不同的操作系统上编写相同的多线程代码。
总的来说,Java中的进程和线程是基于Java虚拟机的概念,与操作系统的进程和线程有一些差异。Java中的线程是轻量级的,创建和销毁的开销较小,共享进程的资源,并通过Java虚拟机的线程调度器实现并发执行。这种跨平台的特性使得Java在多平台开发中具有很大的优势。
我在一个jvm进程里面,最多能开多少个线程,原因是什么?
在一个JVM进程中,最多能够开启的线程数量是有限的,具体数量取决于操作系统和JVM的限制。主要的限制因素包括以下几个方面:
- 操作系统的限制:操作系统对于每个进程所能创建的线程数量有一定的限制。不同操作系统的限制不同,例如在32位的Windows系统中,每个进程最多能创建约2000个线程;而在64位的Windows系统中,每个进程最多能创建约32000个线程。
- JVM的限制:JVM也对线程数量进行了限制,主要是基于操作系统的限制进行调整。JVM会为每个线程分配一定的堆栈空间,因此线程数量的增加会占用更多的内存资源。JVM会根据可用的内存大小和堆栈空间的设置来限制线程的数量。
- 硬件资源的限制:除了操作系统和JVM的限制外,硬件资源也会对线程数量产生影响。每个线程都需要占用一定的CPU时间片和其他硬件资源,当线程数量过多时,可能会导致资源竞争和性能下降。
需要注意的是,开启过多的线程并不一定能提高程序的性能,反而可能会导致线程切换的开销增加,造成性能下降。因此,在设计和实现多线程程序时,需要合理评估和控制线程的数量,避免过多的线程带来的负面影响。
一个线程的创建需要消耗哪些资源呢?
创建一个线程需要消耗一些资源,包括以下几个方面:
- 内存空间:每个线程需要分配一定的内存空间,用于存储线程的栈帧、局部变量和方法调用等信息。线程的栈空间大小可以通过JVM参数进行配置,默认情况下为几百KB到几MB不等。
- 栈空间:每个线程都有自己的栈空间,用于存储方法调用的参数、局部变量和返回值等。栈空间的大小在线程创建时被分配,一般较小,通常为几十KB到几百KB。
- 线程上下文:创建一个线程需要为其分配一个线程上下文,包括线程ID、寄存器状态、程序计数器等。线程上下文的创建和切换会消耗一定的开销。
- 调度和管理开销:线程的创建和管理需要一定的调度和管理开销,包括线程调度器的调度算法、线程状态的维护、线程的优先级管理等。
- 其他资源:线程的创建可能还会涉及其他资源的分配,例如文件描述符、打开的文件、网络连接等,这些资源的消耗与具体的应用场景相关。
需要注意的是,线程的创建和销毁过程相对较为耗时,因此在设计多线程程序时,需要合理评估线程的创建和销毁频率,避免频繁创建和销毁线程带来的性能开销。同时,合理控制线程的数量,避免过多的线程占用过多的内存资源。
http熟悉吗?1.0,1.1,2.0有什么区别?
HTTP/1.0:
- 使用短连接方式,即每次请求都需要建立一个新的TCP连接,请求完成后立即关闭连接。
- 无法复用连接,导致每次请求都需要重新建立连接,增加了延迟和资源消耗。
HTTP/1.1:
- 引入了持久连接(Keep-Alive)的概念,允许多个请求和响应复用同一个TCP连接,减少了连接建立和关闭的开销。
- 支持管道化(Pipeline)技术,允许客户端在一个连接上发送多个请求,而无需等待每个请求的响应。
- 这些改进提高了HTTP的性能和效率。
HTTP/2.0:
- 引入了二进制分帧层(Binary Framing Layer)和多路复用(Multiplexing)等新特性。
- 使用二进制格式传输数据,提高了传输效率。
- 支持在一个连接上同时发送多个请求和响应,允许服务器主动推送数据给客户端,减少了延迟和网络拥塞。
- 引入了头部压缩(Header Compression)和服务器推送(Server Push)等新特性,进一步提升了性能和效率。
总的来说,HTTP/1.0是最早的版本,HTTP/1.1引入了持久连接和管道化技术,HTTP/2.0进一步提高了性能和效率,引入了二进制分帧层和多路复用等新特性。这些改进使得HTTP在传输效率、性能和用户体验方面有了显著的提升。
3.0也出来啦,3.0解决了什么问题呢?
HTTP/3解决了一些HTTP/2存在的问题:
- 解决了队头阻塞(Head-of-Line Blocking)问题:在HTTP/2中,如果一个请求或响应在传输过程中丢失或延迟,后续的请求或响应都会被阻塞,导致整体性能下降。而HTTP/3使用QUIC协议,它基于UDP而不是TCP,可以避免队头阻塞问题。
- 提高了连接的建立速度:在HTTP/2中,建立一个新的连接需要进行TLS握手和TCP三次握手,这会增加连接的建立时间。而HTTP/3使用QUIC协议,它在连接建立时可以同时进行TLS握手和连接建立,减少了连接建立的延迟。
- 提高了网络切换的适应性:在移动网络环境下,网络切换是常见的情况。HTTP/3使用QUIC协议,它可以更好地适应网络切换,减少了连接的中断时间。
总的来说,HTTP/3通过使用QUIC协议解决了HTTP/2存在的队头阻塞问题,提高了连接的建立速度和网络切换的适应性。这些改进有助于提升HTTP在移动网络环境下的性能和用户体验。
建议
大致看了下面经,考题其实都很常规普通,建议在实习和秋招提前批不断面试总结
、看书、研究面经、和面试官请教
、查资料查漏补缺,不断熟悉面试的基本套路
和问题模式
把每次的点点滴滴都总结记载了下来,每次面前复习预热
。
比如项目实习: 我提前便研究好了可能问的问题,对可能问的问题准备了几套回答,首先说基本回答,等面试官问优化思路再把准备的第二套方案拿出来,这样,一切在自己掌控之中!
又比如:MySQL知识,你可能问某个问题,我便把相关原理和场景都给你准备好啦,就等着面试官提 MySQL 这个词,然后一套装逼就来啦!!
然后遇到面试问到的什么基础,场景
,项目实习
,优化
啥的,都会总结记录,然后针对该问题查询专栏书籍等进行总结,下次面试就是一套装逼过去!就这样,大家口中的【面试笔记】可以点击查看就诞生啦!!
可能网络上的什么八股文资料很多,无可厚非,你能把那些资料都理解,随便进大厂大概率没问题。但是个人觉得,那些资料每个知识都是一大偏文章
,然后回答方式
还特官方
(我当时开始也背过网上的资料,但是随便一个问题都官方的一大片,不适合我这种懒记性差
,面试还紧张的一个字都挤不出来的人)
然后一个知识一个pdf,我遭不住,这咋记得住。而且没针对没对策
,没重点,不知道从何背起
,也不知道面试是否真的要考,我当时就是这个心态。收集了一大堆八股资料然后就没然后啦!!
而且对于一个问题,网上的帖子资料众说纷纭,你一个问题查大半天的资料甚至都还不知道哪个是对的哪个是错的
(别问我为啥知道,我都经历过)
而【面试笔记】可以点击查看上的总结归纳,我每次面试除了灵活一点的场景设计题和一些略偏的问题,基本每次面试我都按照我总结的回答的(回答是每次面试和无数次查询实践得出的结论,我觉得都是对的也很深入,逼格足够高啦
,反正我每次面试就这样回答的)
然后也每次回答的起飞
,除开算法和难一点的场景分析,我基本没有不会的(不是说简单的回答基本,而是BAT面试官喜欢的原理实质!)
比如:你问我事务是啥?我给你扯到ACID实现原理!牵扯到的离级别实现原理、顺带说一下幻读(自己亲自实验的过程)、再谈谈日志,扯到日志原理,不就又到了binlog,然后我再给你扯扯两阶段提交,主从原理之类的,这样时间也凑够了,逼格也高。【手动狗头】
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net