滴滴面经1
参考牛客面经问题
1. synchronized底层实现
–
Java 早期版本中
- synchronized属于重量级锁,效率低下
- synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。
当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。- 因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。
synchronized 同步方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用
与Lock / ReentrantLock 的区别
- 两者都是可重入锁
两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。- synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
synchronized 是依赖于 JVM 实现的,JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。
ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成(避免死锁)),所以我们可以通过查看它的源代码,来看它是如何实现的。- ReentrantLock 比 synchronized 增加了一些高级功能
主要来说主要有三点:
①等待可中断;
②可实现公平锁;
③可实现选择性通知(锁可以绑定多个条件)
- ReentrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
- ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。
- synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
2. 往map里插入一个student,修改这个student的name属性,是否还能get到
–
HashMap<Object, String>map; class Student{ String name; }
–
能。因为改变属性的引用,不会改变对象的引用的地址。
最经典的就是用对象做锁,修改对象属性不会释放锁
3. 常量池
String a = “1”;
int b = 1;
问:a.equals(b)?,为什么
–
false。因为a的value是49,b是1;
4. 如何缓解哈希碰撞
- 链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位;
- 开放地址法是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位。
–
HashMap采用的链表法的方式
- 判断数组是否为空,为空进行初始化;
- 不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index;
- 查看 table[index] 是否存在数据,没有数据就构造一个Node节点存放在 table[index] 中;
- 存在数据,说明发生了hash冲突(存在二个节点key的hash值一样), 继续判断key是否相等,相等,用新的value替换原数据(onlyIfAbsent为false);
- 如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中;
- 如果不是树型节点,创建普通Node加入链表中;判断链表长度是否大于 8, 大于的话链表转换为红黑树;
- 插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍。
–
【扩展】拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,链表长度低于6,就把红黑树转回链表,因为根本不需要引入红黑树,引入反而会慢。
5. TCP、HTTP协议
HTTP(超文本传输协议,HyperText Transfer Protocol)
定义了浏览器和Web服务器之间传输的报文格式和序列
HTTP使用 TCP作为运输层协议
- 客户向它的套接字接口发送HTTP请求报文,并从它的套接字接口接收HTTP响应报文
- 服务器从它的套接字接口接收HTTP请求报文和向它的套接字接口发送HTTP响应报文
–
无状态协议
–
非持久连接和持久连接:
单独TCP连接 or 相同TCP连接
非持久连接缺点:
- 给每个请求的对象 建立和维护 全新连接 Web服务器负担大
- 每个对象经受两倍RTT的交付时延
TCP
- TCP 提供可靠的,面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。
- TCP 不提供广播或多播服务。
- TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。
TCP 协议如何保证可靠传输
- 三次握手四次挥手
- 数据分块,给每个包编号
- 校验和: TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段
- TCP 的接收端会丢弃重复的数据。
- 流量控制: TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)
- 拥塞控制: 当网络拥塞时,减少数据的发送。
- ARQ协议: 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
- 超时重传: 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
三次握手
- 传递数据之前,会有三次握手来建立连接
确认自己与对方的发送与接收是正常的
第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
所以三次握手就能确认双发收发功能都正常,缺一不可。
第2次握手传回了ACK,为什么还要传回SYN?
接收端传回发送端所发送的ACK是为了告诉客户端,我接收到的信息确实就是你所发送的信号了,这表明从客户端到服务端的通信是正常的。而回传SYN则是为了建立并确认从服务端到客户端的通信。”
四次挥手
断开一个 TCP 连接则需要“四次挥手”:
- 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
- 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号
- 服务器-关闭与客户端的连接,发送一个FIN给客户端
- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1
举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。
在浏览器中输入url地址 ->> 显示主页的过程
- 准备:DHCP、UDP、IP、以太网
- 本地客户端DHCP请求报文->UDP请求报文->IP数据报
- 放置以太网帧,广播交换机连接的设备
- 交换机 所以出端口 广播
- 路由器接收,载荷被分解 DHCP服务器获得请求报文
- DHCP服务器生成 IP,DNS服务器IP,默认网关路由器IP,子网掩码 DHCP ACK报文段
- 发送给交换机,交换机转发
- 抽取 DHCP客户端记录 IP,DNS的IP,默认网关的IP
- 仍在准备:DNS ARP
- DNS查询报文->UDP请求报文->IP数据报
- 放入以太网帧,为获得网关路由器MAC地址,ARP协议
- ARP查询报文,放入以太网帧,广播
- 网关路由器接收,将ARP回答放入以太网帧,向交换机发送
- 收到ARP回答报文,抽取网关路由MAC地址
- 现在能使DNS查询的以太网帧寻址到网关路由器的MAC地址
- 仍在准备:域内路由选择到DNS服务器
- 网关路由器查找给数据报的目的地址,根据转发表,决定给数据报的下一跳路由器
- 转发表根据域内协议(RIP,OSPF)和域间协议BGP
- 到DNS服务器,查找来自权威DNS服务器的 DNS源记录,形成DNS回答报文
- 从DNS报文抽取访问页面IP
- Web客户 - 服务器交互:TCP和HTTP
- TCP报文段->目的IP地址的IP数据报
数据报->MAC地址为网关路由器的帧
向交换机发送- 到达目的IP 抽取TCP SYN,生成连接套接字,产生TCP SYNACK报文段
- 浏览器生成包含URL的HTTP GET报文,写入套接字,GET报文成为一个TCP报文段的载荷
- HTTP 服务器 从 TCP套接字 读取HTTP GET报文,生成HTTP响应报文
6. spring MVC如何处理前端发送来的请求
- 客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
- DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
- 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
- HandlerAdapter 会根据 Handler 来调用真正的处理器来处理请求,并处理相应的业务逻辑。
- 处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
- ViewResolver 会根据逻辑 View 查找实际的 View。
- DispaterServlet 把返回的 Model 传给 View(视图渲染)。
- 把 View 返回给请求者(浏览器)
7.说说5个你用过的注解
SpringBoot+Spring常用注解
@SpringBootApplication
@Autowired:自动导入对象到类中,如:Service 类注入到 Controller 类中.让 Spring 容器帮我们自动装配 bean。
- 要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,可以采用以下注解实现:
- @Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。
- @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
- @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
- @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。
@RestController:@Controller和@ResponseBody的合集,REST 风格的Controller,默认返回JSON
@Configuration:一般用来声明配置类
@GetMapping,@PostMapping @PutMapping @PutMapping
@PathVariable用于获取路径参数
@RequestParam用于获取查询参数。
@RequestBody