minds
稳住就能赢
- 今天提交了会议论文的摘要,然后学习了mq的五种消息模型;
一、面经
每天要记录一个面经的学习内容,今天学习的面经是:3.19 百度后端面试;
01、HTTP
在浏览器输入URL会发生什么?
- 在浏览器输入URL地址后,首先会去根据URL的地址进行DNS解析,找到对应的服务器地址;
- 然后让浏览器和服务器建立TCP连接;
- 浏览器发送请求给服务器,服务器返回响应给浏览器;
- 浏览器解析响应并渲染页面;
02、TCP
说一下TCP四次挥手:
- 如果A和B建立了TCP连接后,A想要断开此连接,就需要四次挥手来断掉A和B之间的连接;
- 首先A会给B发送一个报文,标志位为FIN,序列号为a;
- B接收到A发过来的报文,知道了A想要断开连接的意思,就给A发送标志位为ACK,确认A发送过来的序列号有效,还将自己的序列号发送给A;
- 这段时间内,B还需要将A上次请求的数据发送过去,当B处理完A断开连接前的所有请求后,再次给A发送标志位为FIN的请求报文,表明B可以彻底断开连接了;
- A接收到B发送过来的报文后,确认B发送过来的断开连接报文有效,给B返回响应报文;
- B接收到后,至此A和B断开了TCP连接;
建立TCP连接后,客户端下线了会发生什么?
- 在服务器端,有一个保活机制,确保客户端下线后,服务端能够自动断开TCP连接;
- 这个保活机制的原理是:
- 服务端有一个规定的时间,如果超过了这段时间,server会主动给client发送一个报文,询问client是否还在线;
- server不会只发送一次这个验证存活的报文,保活机制规定了server要每次间隔多长时间发送多少次,如果在规定的次数内,client都没有对server发送的报文做出响应,就证明client下线了;
- server就会自动断开这个和client的连接,释放资源;
03、JUC
了解过jvm内存模型吗?
- jvm内存模型的目的是为了规范每个线程对计算机系统内存中变量的读写操作,解决高并发下可能出现的三个问题,保证操作的可见性、有序性和原子性;
- Java内存模型,是将每个线程内部划分一块区域,为工作内存,如果线程需要对主内存中的变量进行操作,需要将主内存中的变量读取拷贝一份到工作内存中,也就是说,关于数据的任何修改操作,都是发生在线程工作内存内,并不涉及到主内存区域;
- 关于工作内存和主内存的交互,Java内存模型定义了八个步骤:
- 假设线程A想要对主内存中的变量B进行修改,为了保证线程安全,首先将数据B进行lock,保证其他的线程不能对该变量进行任何修改了;
- 然后线程A将B变量read到线程A,并load进入线程A的工作内存中,那么线程A此时就会获得一份关于变量B的数据副本了;
- 然后use这个副本,通过代码执行引擎给副本进行assign;
- 得到副本的新值后,会存储到工作内存中,使用store,再通过write写回到主内存中;
- 线程A对变量B的执行操作结束后,会unlockB变量,其他的线程就可以继续访问并修改B变量了;
- 在进行主内存和线程的工作内存交互过程中,始终要注意happens-before原则:
- 在一个程序中,前面的代码永远是happens before之后的代码的;
- 对于数据的解锁永远happens-before数据的加锁;
- 被volatile关键字修饰的数据,写happens-before读;
- 并且happens-before具有传递性,如果A happens-before B,B happens-before C,那么一定有A happens-before C;
jdk1.8中的concurrentHashMap是怎么实现线程安全的?
- concurrentHashMap的底层数据结构和hashmap是一致的,都是采用table数组和链表、红黑树的结构;
- concurrenthashmap中采用了cas和synchronized关键字,保证并发操作时候的线程安全问题;
- 在没有指定数据容量的时候,默认数组大小是16,扩容机制为变为原来容量的2倍;
- 在进行添加元素时候,首先会判断key和value是否为null,如果为null,则会抛出空指针异常;
- 然后判断table数组是否为空,进行初始化操作;
- 初始化结束后,根据hash扰动算法,得到key在table数组中的桶位;
- 拿到桶位第一个元素,如果桶位第一个元素为空,那么直接利用CAS将键值对插入到桶位中;
- 如果桶位第一个元素不为空,并且数组正处在元素扩容迁移的时候,那么当前线程就有义务去帮助数组进行扩容迁移;
- 如果桶位第一个元素不为空,并且数组又不处于迁移扩容,那么就需要将当前桶位,利用synchronized关键字上锁,保证只有一个线程能访问该桶位中的所有元素;
- 判断当前桶位是链表还是红黑树,进行替换或者新增,替换或者新增结束后,判断是否需要将桶位中的元素进行树化;
- 最后将判断数组是否需要扩容;
threadLocal中的底层结构了解过吗?是怎么实现的?
- threadLocal是每个线程都有一个专门存储变量的地方,保证了只有当前线程才能访问,使数据隔离,保证了数据的线程安全;
- threadLocal底层中其实维护了一个threadLocalMap,threadLocal本身不存储变量,是将需要存储到threadLocal中的变量value,存储到threadLocalMap中,然后将threadLocal引用这个存储到threadLocalMap中的value;
- 通过阅读源码可以发现,threadLocal是一个弱引用关系,会导致内存泄漏;
- 因为被弱引用着的变量,只要发生垃圾回收都会被清理掉,而存储到threadLocalMap中的value是一个强引用类型,只能手动去删除清理掉,才会被垃圾回收;
- 为了避免threadLocal可能出现的内存泄漏,在代码编写的过程中,一定要及时的释放掉存储到threadLocal中的value;
- 在项目中,登录拦截器,就采用了threadLocal,将用户信息放到threadLocal中,最后从拦截其中出来前,将用户信息清理掉;
04、SE基础
Object类有哪些方法?
- getClass:得到当前对象的Class实例;
- hashcode:得到当前对象的hash码值;
- equals:比较两个对象是否是相等;
- wait:让当前用到这个对象的线程等待;
- notify:唤醒使用着这个对象的线程;
- notifyAll:唤醒所有被阻塞着的线程;
- toString:返回该对象的字符串;
- clone:拷贝这个对象;
arrayList的源码阅读过吗?简单介绍一下吧
- 首先,arrayList他底层是维护了一个动态可扩容的数组,如果没有指定初始化容量大小,那么默认是10,之后每次扩容都是原来的1.5倍;
- 在arrayList中,默认是一种懒加载机制,真正进行数组初始化的时机是加入第一个元素的时候,才会初始化数组,每次将元素放入数组中,最后都会进行判断是否需要扩容的方法;
hashmap能push一个键值对都为null的元素吗?
- 是可以的,在hashmap的源码中,要存储一个key-value键值对的话,首先会将key通过hash扰动算法得到该key在table数组中的桶位;
- 当key为null的时候,每次得到的桶位都是一样的,所以只能存储一个key为null的键值对;
- 但是value为null的可以存储好多个,因为key不同就行了;
二、力扣
01、对称的二叉树
思路:
- 画个图,利用上递归,需要判断base case是什么;
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
// 判断 root 为头节点的二叉树是不是对称的
public boolean isSymmetric(TreeNode root) {
return root == null ? true : isOrNot(root.left, root.right);
}
boolean isOrNot(TreeNode A, TreeNode B){
if(A == null && B == null){
return true;
}
if(A == null || B == null || A.val != B.val){
return false;
}
return isOrNot(A.left, B.right) && isOrNot(A.right, B.left);
}
}