21、TCP的四次挥手?
在断开TCP连接时,需要通过四次挥手来断开,过程是:
(1)客户端向服务端发送FIN=1和序列号SEQ=x的数据包,用来关闭客户端到服务端的数据传送。然后客户端进入 FIN-WAIT-1 状态。
(2)服务端接收FIN后,向客户端发送ACK(ACK=x+1),表示我接收到了断开连接的请求,客户端可以不发数据了,不过服务端这边可能还有数据正在处理。这时候然后服务端进入 CLOSE-WAIT 状态,客户端收到ACK确认号后进入 FIN-WAIT-2 状态。
(3)服务端处理完所有数据后,向客户端发送FIN=1和序列号SEQ=y的数据包,表示服务端现在可以断开连接,然后服务端进入 LAST-ACK 状态。
(4)客户端接收到服务端的FIN,向服务端发送ACK(ACK=y+1),表示客户端也会断开连接。客户端进入TIME-WAIT状态,服务端在收到 ACK (ACK=y+1)标志的数据包后进入 CLOSE 状态。此时如果客户端等待 2MSL 后依然没有收到回复,就证明服务端已正常关闭,随后客户端也进入CLOSE状态。
22、浏览器输入URL的过程?
- 在浏览器中输入指定网页的 URL。
- 浏览器通过 DNS 协议,获取域名对应的 IP 地址。
- 浏览器根据 IP 地址和端口号,向目标服务器发起一个 TCP 连接请求。
- 浏览器在 TCP 连接上,向服务器发送一个 HTTP 请求报文,请求获取网页的内容。
- 服务器收到 HTTP 请求报文后,处理请求,并返回 HTTP 响应报文给浏览器。
- 浏览器收到 HTTP 响应报文后,解析响应体中的 HTML 代码,渲染网页的结构和样式
- 浏览器在不需要和服务器通信时,可以主动关闭 TCP 连接,或者等待服务器的关闭请求
22、服务器如何主动地给客户端发消息?
WebSockets 是一种在单个 TCP 连接上实现全双工通信的协议。这意味着服务器和客户端可以同时向彼此发送数据,而不像传统的 HTTP 请求-响应模式那样,需要客户端首先发起请求才能获取服务器的响应。
WebSockets 的工作流程大致如下:
- 握手阶段:客户端发起一个特殊的 HTTP 请求,其中包含一些特定的头部信息,表明它希望升级到 WebSocket 连接。
- 连接建立:服务器接收到握手请求后,如果同意升级到 WebSocket,将发送一个响应,随后客户端和服务器之间的连接就建立起来了。
- 双向通信:一旦 WebSocket 连接建立,服务器和客户端可以通过该连接双向发送消息,而不需要额外的 HTTP 请求。这种方式非常适合实时应用,如聊天应用、实时游戏、股票市场更新等需要快速响应和更新数据的场景
23、hashmap用了哪些数据结构?
在JDK1.7 中,由“数组+链表"组成,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。
在jdk1.8版本后,底层是由“数组+链表+红黑树"组成。java对HashMap做了改进,在链表长度大于8的时候,会把链表转换为红黑树,但是如果数组的长度小于64,会先对数组进行扩容。
24、链表转红黑树的目的?
为了加快检索速率。红黑树虽然本质上是一颗二叉树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。
25、hashmap是线程安全的吗?
HashMap在并发场景中不是线程安全的。
1、多线程操作造成死循环问题
在jdk1.7的hashmap中在数组进行扩容的时候,因为链表是头插法,在进行数据迁移的过程中,有可能导致死循环。
2、多线程造成数据覆盖问题
多个线程对 HashMap 的 put 操作会导致线程不安全,具体来说会有数据覆盖的风险。比如A希望插入一个key-value对到HashMap中,当获取到对应的链表结点位置时,此时线程A的时间片用完了而此时线程B被调度得以执行,可能线程B占用了A计算得到的位置,插入了数值。而线程A被切换回来的时候,不知道B已经插入了元素,仍然将元素插入此前计算好的位置,这样就会将B线程的插入记录覆盖掉了
26、hash的过程?
首先根据key的值计算出hashcode的值,然后根据hashcode计算出hash值,最后通过hash&(length-1)计算得到存储位置。
27、多线程下使用hashmap保证线程安全的方案?
1、HashTable 是线程安全的。HashTable 容器使用 synchronized 来保证线程安全,但在线程竞争激烈的情况下 HashTable 的效率非常低下。
2、使用ConcurrentHashMap
28、聊-聊concurrentHashMap?
ConcurrentHashMap 是一种线程安全的高效Map集合,jdk1.7和1.8也做了很多调整。
- JDK1.7的底层采用是分段的数组+链表 实现
- JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。
在jdk1.7中 ConcurrentHashMap 里包含一个 Segment 数组,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构 的元素。JDK1.7采用Segment分段锁,底层使用的是ReentrantLock,是一种可重入锁,当我们对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁
在jdk1.8中的ConcurrentHashMap 做了较大的优化,性能提升了不少。首先是它的数据结构与jdk1.8的hashMap数据结构完全一致。其次是放弃了Segment的设计,取而代之的是采用Node + CAS + Synchronized来保 证并发安全进行实现,CAS是对添加数组结点的时候保证线程安全,采用synchronized锁定链表或红黑二叉树的首节点,相对Segment分段锁粒度更细,性能更好。
29、线程池的核心参数?
30、拒绝策略有哪些?
1、AbortPolicy:直接抛出异常,默认策略。
2、CallerRunsPolicy:用调用者所在的线程来执行任务
3、DiscardOldestPolicy:丟弃阻塞队列中最靠前的任务,并把当前任务放入阻塞队列中。
4、DiscardPolicy:直接丢弃任务。
31、bean的生命周期?
嗯!,这个步骤还是挺多的,我之前看过一些源码,它大概流程是这样的
首先会通过一个非常重要的类,叫做BeanDefinition获取bean的定义信息,这里面就封装了bean的所有信息,比如,Bean的类名、作用域、依赖、初始化方法
在创建bean的时候,第一步是调用构造函数实例化bean
第二步是bean的依赖注入,Spring容器会通过依赖注入将Bean所需的依赖注入到Bean的相应属性,依赖注入可以通过构造函数、setter方法、字段注入等方式完成。
第三步是处理Aware接口,如果某一个bean实现了Aware接口就会重写方法去执行,将容器的信息(如Bean的名称、BeanFactory、ApplicationContext等)注入到Bean中。
第四步是bean的后置处理器BeanPostProcessor的前置
- 如果有注册的BeanPostProcessor实现类,Spring容器会在调用Bean的初始化方法之前,先调用这些BeanPostProcessor的前置处理方法。这些后置处理器可以在Bean初始化前后对Bean进行一些定制化的操作。
第五步是初始化方法,比如实现了接口InitializingBean或者自定义了方法init-method标签或@PostContruct
第六步是执行了bean的后置处理器BeanPostProcessor的后置。
最后一步是销毁bean
32、MySQL的最左匹配原则?
最左前缀匹配原则指的是我们在使用联合索引时,查询会根据联合索引中的从左到右的字段顺序来进行匹配,否则的话联合索引就会失效。
33、索引失效的情况?
嗯,这个情况比较多,我说一些自己的经验,以前遇到过的
比如,索引在使用的时候没有遵循最左匹配法则,第二个是,模糊查询,如果以%开头的话也会导致索引失效。第三个是如果在添加索引的字段上进行了运算操作或者类型转换也都会导致索引失效。
我们之前还遇到过一个就是,如果使用了复合索引,中间使用了范围查询,右边的条件索引也会失效
所以,通常情况下,想要判断出这条sql是否有索引失效的情况,可以使用explain执行计划来分析。
34、内连接、左右连接的区别?
35、Springboot事务管理?
事务是指一组操作,这些操作要么全部成功,要么全部失败。如果在一组操作中有一个操作失败了,那么整个事务都应该回滚,即撤销已经执行的操作,从而保证数据的一致性和完整性。
36、线程池的执行流程?
37、对volatile关键字的理解?
volatile是我们java的一个关键字,一旦一个共享变量被volatile修饰之后,那么就具备了两层语义:
1、保证线程间的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的
2、可以禁止指令重排序,用 volatile 修饰共享变量会在读、写时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果。
volatile修饰的变量,上方的其他写操作不能越过他的写操作排到他的下面,下方的其他读操作不能越过他的读操作排到他的上面。
38、如何理解]ava并发中的可见性?
Java并发可见性指的是多线程并发访问共享变量时,对变量的更改能够被其他线程及时感知,即在一个线程修改变量后,其他线程能够立即看到这个变量的修改结果。
在Java中,可以使用volatile关键字来保证变量的可见性,对于加了volatile的变量,线程在读取该变量时会直接从内存中读取,再修改该变量时会同时修改CPU高速缓存和内存中的值。
39、Volatile和Synchronized的区别?
(1)作用的位置不同,synchronized是修饰方法和代码块。volatile是修饰变量。
(2)作用不同:synchronized,可以保证变量修改的可见性及原子性,可能会造成线程的阻塞,synchronized在锁释放的时候会将数据写入主内存,保证可见性。 volatile仅能实现变量修改的可见性,但无法保证原子性,不会造成线程的阻塞,volatile修饰变量后,每次读取都是去主内存进行读取,保证可见性。
40、hashcode和equals的问题?
1、对于hashcode方法,会返回一个哈希值,哈希值对数组的长度取余后会确定存储的下标位置。
不同的哈希值取余之后的结果可能是相同的,用equals方法判断是否为相同的对象,不同则在链表中插入,
40、为什么重写 equals 方法必须重写 hashcode 方法 ?
判断的时候先根据hashcode进行的判断,相同的情况下再根据equals()方法进行判断。如果只重写了equals方法,而不重写hashcode的方法,会造成hashcode的值不同,而equals()方法判断出来的结果为true。这样这两个相同的对象,就会被映射到不同的位置,从而造成相同对象的不能覆盖的问题。
41、对反射机制的理解?
1、定义:
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。这种动态获取类的信息以及动态调用对象的方法的功能称为Java 语言的反射机制。
2、优缺点:
优点:反射让我们在运⾏时有了分析操作类的能⼒,提高灵活性
缺点:使用反射性能较低,需要解析字节码,将内存中的对象进行解析。其解决方案:通过setAccessible(true)关闭JDK的安全检查来提升反射速度,多次创建一个类的实例时,有缓存会快很多。
3、如何获取反射中的Class对象?
①Class.forName(“类的路径”);当我们知道该类的全路径名时,就可以使用该方法获取 Class 类对象。
②类名.class。这种方法只适合在编译前就知道操作的 Class。
③对象名.getClass()。
④通过类加载器xxxClassLoader.loadClass(),传入类路径获取
42、JVM的垃圾回收算法?
(1)标记清除算法
标记阶段:把垃圾内存标记出来,
清除阶段:直接将垃圾内存回收。这种算法是比较简单的,但是有个很严重的问题,就是会产生大量的内存碎片。
(2)复制算法:
为了解决标记清除算法的内存碎片问题,复制算法将内存分为大小相等的两半,每次只使用其中一半。垃圾回收时,将当前这一块的存活对象全部拷贝到另一半,然后当前这一半内存就可以直接清除。这种算法没有内存碎片,但是他的问题就在于浪费空间。而且,他的效率跟存活对象的个数有关。
(3)标记压缩算法:
为了解决复制算法的缺陷,就提出了标记压缩算法。这种算法在标记阶段跟标记清除算法是一样的,但是在完成标记之后,不是直接清理垃圾内存,而是将存活对象往一端移动,然后将边界以外的所有内存直接清除。
43、Hotspot为什么要分为新生代和老年代?
HotSpot根据对象存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记一清理“或者“标记一整理“算法来进行回收。
其中新生代又分为1个Eden区和2个Survivor区,通常称为From Survivor和TOSurvivorx.