提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
1.“==”和equals的区别?
“==”是运算符,如果是基本数据类型,则比较存储的值;如果是引用数据类型,则比较所指向对象的地址值。
equals是Object的方法,比较的是所指向的对象的地址值,一般情况下,重写之后比较的是对象的值。
2.sleep和wait的区别?
- sleep方法是Thread类的一个静态方法,而wait方法是Object类的一个实例方法。这意味着sleep可以在任何线程中使用,而wait必须在synchronized的同步方法或代码块中使用。
- sleep方法会在指定的时间后自动唤醒线程,不需要其他线程的干预。而wait方法则需要其他线程调用notify()或notifyAll()方法来唤醒等待的线程。
- sleep方法不会释放任何锁,即使线程持有某个对象的锁并调用sleep,其他线程仍然无法访问该对象,直到sleep结束。而wait方法会释放对象锁,允许其他线程访问该对象。
在实际使用中,sleep通常用于让线程暂停一段时间,而不关心其他线程的状态或活动。而wait方法通常用于线程间的通信、同步和协调。
问题深入:
1).wait方法为什么只能在synchronized使用?
- 线程安全:wait()、notify()和notifyAll()方法必须在同步块或同步方法中调用,以确保在调用这些方法时,当前线程已经获取了对象的监视器锁,这样才能对锁进行释放或唤醒等操作。这保证了在共享资源上的操作是线程安全的。
- 监视器锁的释放和恢复:当一个线程调用wait()方法时,它会释放当前持有的监视器锁,让其他线程能够获得该锁并执行相关操作。当调用notify()或notifyAll()方法时,被唤醒的线程会重新竞争获取锁,一旦获取到锁,才能继续执行。
- 线程间通信:wait()、notify()和notifyAll()方法是实现线程间通信的关键。通过调用wait()方法,线程可以等待某个条件的满足;而通过notify()或notifyAll()方法,线程可以通知其他等待的线程条件已经满足,从而让它们继续执行。
2).run和start的区别?
start:我们创建好线程之后,想要启动这个线程,则需要调用其start方法。所以,start方法是新建一个线程的入口。
run:如果在创建好线程之后,直接调用其run方法,那么就会在单线程中直接运行run方法,不会起到多线程的效果。
3).notifyAll和notify的区别?
使用notifyAll,可以唤醒所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个。
但是唤醒的这些线程只是进入争夺队列,表示他们可以竞争锁了,竞争到锁之后才有机会被CPU调度(wait方法被调用的时候,线程已经释放了对象锁)。notifyAll虽然可以把所有线程都唤醒,让他们都可以竞争锁,但是最终也只有一个可以获得锁并执行。
notify和notifyAll因为也是操作对象的,所以把他们定义在Object类中。
3.深拷贝和浅拷贝的区别?
浅拷贝:创建一个新的对象,这个对象有着原始对象属性的值的拷贝。如果属性是基本类型,则拷贝的就是基本类型的值;如果属性是引用类型,则拷贝的就是对该对象的引用,即原始对象与复制对象共享一个内存地址。
深拷贝:创建一个新的对象,并将原始对象的所有属性或元素都复制到新的对象中,不论是基本类型还是引用类型,都是全新的拷贝。如果修改复制对象中的属性或元素,原始对象中对应的属性或元素不会受到影响。
问题拓展:
- 实现浅拷贝:BeanUtils.copyProperties()
- 实现深拷贝:序列化。
JSON.toJSONString()
JSON.parseObject()
4.动态代理常见的应用场景以及优缺点?
动态代理:是一种在运行时动态生成代理对象的技术。
有两个角色:目标对象(待增强的对象)和代理对象(增强后的对象,也是我们会使用的对象)
代理模式:是一种设计模式,用于在不修改原始对象的情况下,通过代理对象来间接访问原始对象,并在访问前后执行额外的操作。
- 静态代理:在编译期间确定好代理关系。
- 动态代理:在运行期间确定好代理关系。
* jdk动态代理(反射)
* cglib代理(继承)
动态代理通常用于实现横切关注点,如日志记录、性能监控、事务管理等,能够在不改变原始对象的情况下,通过代理对象在方法调用前后插入额外的逻辑。
应用场景: - AOP(面向切面编程):动态代理可以实现横切关注点的功能,如日志记录、性能监控、事务管理等。通过在方法调用前后插入额外的逻辑,可以实现对原始对象的控制和增强。
- 远程方法调用(RPC):动态代理可以将远程方法调用封装为本地方法调用,简化远程通信的操作。通过动态代理,开发人员可以像调用本地对象一样调用远程对象的方法。
- 消息中间件:动态代理可以用于消息中间件的发布/订阅模型。通过代理对象,可以将消息发送到消息队列并订阅特定的消息,实现解耦和灵活的消息处理。
- 数据库连接池:动态代理可以用于数据库连接池的管理。通过代理对象,可以在获取数据库连接时添加连接池的管理逻辑,如创建、销毁和监控连接。
- 缓存:动态代理可以用于实现缓存的功能。通过代理对象,在方法调用前先检查缓存中是否存在结果,避免重复计算或访问。
优点:动态代理具有灵活性和可扩展性,可以在运行时创建不同类型的代理对象,无需事先知道具体的被代理类。
缺点:动态代理的性能相对较低,因为在每次方法调用时都需要进行额外的逻辑处理。
5.常用的设计模式以及使用场景?
策略
工厂
模板
6.介绍ThreadLocal?
Thread local是线程本地变量,如果创建了一个threadlocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到了线程隔离的作用,避免了线程安全问题。(可以让每个线程拥有自己的变量,而不是共享同一个变量)
实现:通过一个哈希表来存储每个变量和线程的对应关系。哈希表的键就是线程的标识,值就是线程的变量,当一个线程调用get方法时会根据线程的标识,从哈希表中查找对应的变量返回给线程,调用set方法时会根据线程的标识,把变量存到哈希表中覆盖原来的变量,这样每个线程就可以通过thread local来访问自己的变量,而不会影响其他线程的变量。
使用:创建new;写入set;和读取get.
应用场景:
①线程池技术:使用线程池执行多个任务时,为了避免线程间数据冲突可以使用threadlocal存储每个线程独有的数据,这样就可以安全地在多个线程之间共享线程池;
② web应用程序:在web应用中,每个请求通常都会被分配到不同的线程处理, threadlocal可以用来存储当前请求的上下文信息,比如用户ID请求时间等,这些信息可以在同一个请求处理过程中多次使用,但是不同请求之间是互相独立的;
③数据库连接:在多线程环境下,为了避免每个线程都去创建和销毁数据库连接,可以使用连接池技术。使用thread local可以将连接池中的数据库连接与当前线程绑定,确保每个线程都能够得到自己独有的数据库连接,避免数据混乱和线程安全问题。
7.TCP和UDP的区别?
- 是否连接:UDP无连接,TCP面向连接(使用tcp协议进行通信前先建立连接,使用后再释放)。
- 是否可靠:UDP不可靠传输,TCP可靠传输,使用流量控制和拥塞控制。
- 连接对象个数:UDP支持一对一,一对多,多对多、多对一;TCP只能是一对一。
- 传输方式:UDP面向报文,TCP面向字节流。
- 首部开销:UDP首部开销小,仅8字节;TCP最小20字节。
- 场景:UDP适用于实时应用(如IP电话视频会议等),TCP适用于要求可靠传输的应用(如文件传输)
- TCP提供全双工通信:通信双方可以同时传输数据。
拓展:如何理解UDP面向报文,TCP面向字节流?
- UDP是面向报文的: 发送方 UDP 对应用程序交下来的报文,在添加首部后就向下交付 IP 层。UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。接收方 UDP 对 IP 层交上来的 UDP 用户数据报,在去除首部后就原封不动地交付上层的应用进程,一次交付一个完整的报文。
- TCP 则将数据视为一个连续的字节流,可以根据需要自行决定如何组织这些数据。TCP可以根据数据块的大小和传输效率进行合并或拆分,例如,如果应用程序发送的数据块太长,TCP可以将其拆分为多个较小的数据段进行传输;反之,如果发送的数据块太小,TCP也可以等待积累足够的字节后再构成一个较大的数据段进行传输。
8.TCP的三次握手?
具体过程:
第一次握手:客户端要向服务端发起连接请求,首先客户端随机生成一个起始序列号(比如是100),那客户端向服务端发送的报文段包含SYN标志位(也就是 SYN=1),序列号seq=100。客户端验证自己发送能力正常,客户端置为SYN- SENT状态。
第二次握手:服务端收到客户端发过来的报文后,发现SYN=1,知道这是一个连接请求,此时验证客户端发送能力正常,自己接收能力正常,于是将客户端的起始序列号100存起来,并且随机生成一个服务端的起始序列号(比如是300)。然后给客户端回复一段报文,回复报文包含SYN和ACK标志(也就是SYN=1,ACK=1)、序列号seq=300、确认号ack=101(客户端发过来的序列号+1)。发送之后,验证自己发送能力正常,服务端置为syn-rcvd状态。
第三次握手:客户端收到服务端的回复后发现ACK=1并且ack=101,于是知道服务端已经收到了序列号为100的那段报文;同时发现SYN=1,知道了服务端同意了这次连接,于是就将服务端的序列号300给存下来。然后客户端再回复一段报文给服务端,报文包含ACK标志位(ACK=1)、ack=301(服务端序列号+1)、seq=101(第一次握手时发送报文是占据一个序列号的,所以这次seq就从101开始,需要注意的是不携带数据的ACK报文是不占据序列号的,所以后面第一次正式发送数据时seq还是 101)。当服务端收到报文后发现ACK=1并且ack=301,就知道客户端收到序列号为300的报文了,就这样客户端和服务端通过TCP建立了连接。客户端置为established状态。
三次握手的本质是确认通信双方收发数据的能力。首先,我让信使运输一份信件给对方,对方收到了,那么他就知道了我的发件能力和他的收件能力是可以的。于是他给我回信,我若收到了,我便知我的发件能力和他的收件能力是可以的,并且他的发件能力和我的收件能力是可以。然而此时他还不知道他的发件能力和我的收件能力到底可不可以,于是我最后回馈一次,他若收到了,他便清楚了他的发件能力和我的收件能力是可以的。
问题深入:
1)为什么需要三次握手?(问题等同于:两次握手不行吗?)
TCP三次握手验证了client和server的收包和发包能力。
第一次握手:客户端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
第二次握手:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
第三次握手:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。
所以,只有三次握手才能确认双方的接收与发送能力是否正常。如果是两次握手,服务端无法确定客户端是否已经接收到了自己发送的初始序列号,如果第二次握手报文丢失,那么客户端就无法知道服务端的初始序列号,那 TCP 的可靠性就无从谈起。客户端由于某种原因发送了两个不同序号的 SYN 包,我们知道网络环境是复杂的,旧的数据包有可能先到达服务器。如果是两次握手,服务器收到旧的 SYN 就会立刻建立连接,那么会造成网络异常。如果是三次握手,服务器需要回复 SYN+ACK 包,客户端会对比应答的序号,如果发现是旧的报文,就会给服务器发 RST 报文,直到正常的 SYN 到达服务器后才正常建立连接。所以三次握手才有足够的上下文信息来判断当前连接是否是历史连接。
2)四次挥手过程?
四次挥手的目的是关闭一个连接,比如客户端初始化的序列号ISA=100,服务端初始化的序列号ISA=300。TCP连接成功后客户端总共发送了1000个字节的数据,服务端在客户端发FIN报文前总共回复了2000个字节的数据。
第一次挥手:当客户端的数据都传输完成后,客户端向服务端发出连接释放报文 (当然数据没发完时也可以发送连接释放报文并停止发送数据),释放连接报文包含 FIN标志位(FIN=1)、(序列号seq=1101=100+1+1000,其中的1是建立连接时占的一个序列号)。需要注意的是客户端发出FIN报文段后只是不能发数据了,但是还可以正常收数据;另外FIN报文段即使不携带数据也要占据一个序列号。客户端置为FIN-WAIT-1.
第二次挥手:服务端收到客户端发的FIN报文后给客户端回复确认报文,确认报文包含ACK标志位(ACK=1)、确认号ack=1102(客户端FIN报文序列号1101+1)、序列号seq=2300(300+2000)。此时服务端处于关闭等待状态,而不是立马给客户端发FIN报文,这个状态还要持续一段时间,因为服务端可能还有数据没发完。服务端置为CLOSE-WAIT,客户端为FIN-WAIT-2.
第三次挥手:服务端将最后数据(比如50个字节)发送完毕后就向客户端发出连接释放报文,报文包含FIN和ACK标志位(FIN=1,ACK=1)、确认号和第二次挥手一样 ack=1102、序列号seq=2350(2300+50)。服务端置为LAST-ACK.
第四次挥手:客户端收到服务端发的FIN报文后,向服务端发出确认报文,确认报文包含ACK标志位(ACK=1)、确认号ack=2351、序列号seq=1102。客户端置为TIME-WAIT,客户端发出确认报文后不是立马释放TCP连接,而是要经过2MSL(最长报文段寿命的2倍时长)后才释放TCP连接。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,置为CLOSED,所以服务端结束TCP连接的时间要比客户端早一些。
3)四次挥手过程中为什么需要等待2倍的最大报文段生存时间?
1.为了保证客户端发送的最后一个ACK报文段能够到达服务端。客户端发送的ACK报文段可能丢失,因而使服务器收不到对自己已发送的释放连接报文段的确认。服务器会重传连接释放报文段,而客户端就能在2MSL时间内收到这个重传FIN+ACK报文段。接着客户端重传一次确认,重新启动2MSL计时器。最终,客户端和服务器端都能进入CLOSE状态。
2.可以使本连接持续的时间内所产生的所有报文段都在网络中消失。这样就可以在下一个新的连接中不会出现这种旧的连接请求报文段。
4)为什么需要四次挥手?
其实在 TCP 握手的时候,接收端发送 SYN+ACK 的包是将一个 ACK 和一个 SYN 合并到一个包中,所以减少了一次包的发送,三次完成握手。对于四次挥手,因为 TCP 是全双工通信,在主动关闭方发送 FIN 包后,接收端可能还要发送数据,不能立即关闭服务器端到客户端的数据通道,所以也就不能将服务器端的 FIN 包与对客户端的 ACK 包合并发送,只能先确认 ACK,然后服务器待无需发送数据时再发送 FIN 包,所以四次挥手时必须是四次数据包的交互。
9.什么是TCP的粘包、拆包问题?
在进行TCP通信时,因为TCP是面向流的,所以发送方在传输数据时可能会将多个小的数据包粘合在一起发送,而接收方则可能将这些数据包拆分成多个小的数据包进行接收,从而导致数据接收出现错误或者数据粘连的问题。
TCP粘包和拆包问题主要出现在以下两种情况下:
1发送方连续发送多个小数据包:由于TCP是基于流的协议,发送方在传输数据时可能会将多个小数据包组合成一个大数据包进行发送,从而导致接收方在接收数据时无法区分不同数据包之间的界限。
2接收方缓存区大小限制:接收方在接收数据时,如果接收缓存区的大小有限,可能会将一个大的数据包拆分成多个小数据包进行接收,从而导致粘包和拆包问题的出现。
常见的解决方案有三种:
●将业务层协议包的长度固定下来,每个包都固定长度,比如512个字节大小,如果客户端发送的数据长度不足512个字节,则通过补充空格的方式补全到指定长度;
●在每个包的末尾使用固定的分隔符,如换行符/n,如果一个包被拆分了,则等待下一个包发送过来之后找到其中的/n,然后对其拆分后的头部部分与前一个包的剩余部分进行合并即可;
●仿照TCP/IP协议栈,将消息分为header和body,在head中保存有当前整个消息的长度,只有在读取到足够长度的消息之后才算是读到了一个完整的消息。