1.你知道序列化是什么吧?说一下项目中一些类为什么要有一个字段是序列化Id。
-
序列化主要是将Java的数据对象进行转化为二进制,便于网络,磁盘,redis中间体的传输和保存。
-
当然实现序列化只需要在要序列的对象中实现Serializable接口就可以,当然如果该对象中有别的字段是别的对象,那么在别的对象也要去实现这个接口,进而才能达到完整的序列化。
-
在java中对象实际在内存中只是一个引用地址,其具体的内容在jvm堆里面(对象头,字段等),如果要将本jvm的数据传输给别的机器就要进行统一数据序列化处理。
-
当然在Spring项目中使用
@RestController(或@ResponseBody)注解修饰的控制器方法自动处理,Spring Boot 使用默认的 Jackson JSON 序列化库进行处理所以一般我们不用显示的去实现这些序列话接口,但是在存在要保存数据到中间体的话,那么就得对数据进行序列化处理。 -
在序列化中为什么要加这个序列化UID,主要作用是在反序列化过程中,能够校验反序列前后是否一致,因为在分布式环境下,如果数据对象结构稍微一改变,那么对于反序列就可能失败,因此必需在这个里面加对应的序列化UID进行版本校验。
2.IO NIO 之间的区别是什么?
IO 和 NIO本质是两种数据交换的模式,主要区别在于程序是否要等待I/O操作完成。同步IO会阻塞直到当前的操作完成,其余继续执行,异步IO则是在执行IO操作时完成后续的IO操作。
- 模型不同:
- BIO同步阻塞模型,每个连接对应一个线程,线程在执行IO时会被挂起。
- NIO采用异步非阻塞模型,使用选择器(Selector)来监控多个通道(Channel),能够处理多个连接。
- 资源消耗不同:
- BIO线程数连接数成正比,经常阻塞线程上下文切换开销大
- NIO线程数较少,通过事件驱动处理多连接
- 使用场景不同:
- BIO小规模应用,不需要高并发;
- NIO高并发,实时聊天平台;
3.IO多路复用说一下
- 多路复用本质就是允许同一个线程同时处理多个IO请求;多路复用的核心思想在于:使用一个(或少量)线程来轮询或者监控多个资源(如文件描述符、套接字等),一旦某个资源可用(例如可读、可写),则进行相应的处理
- 传统的BIO在处理一个请求后会阻塞当前的线程,直到当前请求处理结束后才轮到下一个;为了解决这个情况,多出了很多个IO多路复用的模型。
- 如:
- Linux的select、poll、epoll
- redis基于epoll的多路复用
- Java的NIO的selector
- netty的NIO模型
- 核心的思想:通过选择器监听多个请求,与多个请求建立连接,使用事件驱动机制将请求分发给处理器进行处理,从而达到处理多个请求不阻塞的效果。
- 处理方式:
- 可以使用线程池来管理多个线程进行并发处理。
- 也可以使用阻塞队列将未处理的请求进行排队,从而保证请求的有序处理。
4.零拷贝技术是什么说一下?
传统的拷贝技术,磁盘于内存要进行多次拷贝和用户态和内核态的切换,非常消耗性能。
![[Pasted image 20250524135218.png]]
零拷贝技术旨在消除用户态和内核态的上下文切换以及对应的内存的拷贝次数,提升IO性能,常用的方法是内存映射文件技术mmap,这种机制在Java中通过MappedByByteBuffer实现,该类来着NIO包中的一个类,用来直接映射文件。
![[Pasted image 20250524135546.png]]
5.浅拷贝和深拷贝是什么?怎么去实现。
-
浅拷贝创建一个新的对象,但是复制着对象的地址引用;
-
深拷贝是直接完全的复制一个新的对象,引用内容不一样
-
Java中是通过去实现Cloneable接口重写clone方法进行实现拷贝
6.线程模型你知道哪些?
reactor:反应器
目前主流的网络框架都采用了I/O多路复用方案(一个线程监听所有的网络连接IO事件是否就绪的模式,就是IO多路复用)。Reactor模式作为其中的事件分发器,负责将读写事件分发给对应的读写事件处理者。
Reactor 模式的组成角色:Reactor 监听IO事件,分配给对应的Handler
Acceptor 处理客户端连接事件并分派请求道处理器链
Handlers 执行非阻塞读/写任务
常用的线程模型:
-
传统BIO阻塞服务模型
![[Pasted image 20250521145816.png]] -
NIO的Reactor模型,根据reactor数量和处理资源池线程数量分成三种类型
- 单Reactor 单线程
![[Pasted image 20250521145704.png|400]]
- 单Reactor 单线程
-
单Reactor 多线程
![[Pasted image 20250521145609.png|400]]
3. 多Reactor 多线程
![[Pasted image 20250521145754.png|400]]
7.平时是怎么处理异常的?异常处理的几个方法是什么?
- 主动抛出 try catch + 日志
- 设置一个异常拦截器 + throws +日志集中处理异常
- try with reasources 每次操作都释放资源
8.能不能讲解一下各大IO模型?
1. 传统 I/O(Blocking I/O)
-
单线程单 I/O
FileInputStream: 读取文件字节的输入流。FileOutputStream: 写入字节到文件的输出流。BufferedReader: 提供字符输入的缓冲功能。BufferedWriter: 提供字符输出的缓冲功能。PrintWriter: 打印格式化文本到输出流。
-
多线程 I/O
FileInputStream/FileOutputStream: 每个线程处理独立的文件。Socket: 用于网络通信的客户端Socket。ServerSocket: 监听客户端连接请求的服务器Socket。Thread: 创建和管理线程。
2. NIO(Non-blocking I/O)
- `InetSocketAddress` 包含了一个 IP 地址和一个端口号,通常用于标识一个网络服务的地址。
- 用 `selector.select()` 时,选择器会检查所有已注册的通道,找出哪些通道有事件发生(如可接受连接、可读数据等)并将其放到HashMap中。这些就绪的 `SelectionKey` 会被添加到 `selectedKeys` 集合中。
- 一般通过 Iterator 进行迭代,迭代器允许在遍历过程中安全地移除当前元素(通过 `remove()` 方法),而不会导致 `ConcurrentModificationException`。这是因为迭代器内部维护了当前状态,确保在移除元素时不会影响到其他元素的遍历。
- 处理完 `selectedKeys` 中的每个键后,一般会使用 `keys.remove()` 方法移除已处理的键。
-
单线程单 I/O
FileChannel: 读写文件的通道。ByteBuffer: 数据的存储和处理容器。SocketChannel: 可读写的Socket通道。ServerSocketChannel: 监听客户端连接的通道。
-
单线程多 I/O(通过 Selector)
Selector: 监控多个通道的I/O事件。SelectableChannel: 可注册到Selector的通道。SocketChannel: 通过Selector进行非阻塞网络I/O。ServerSocketChannel: 通过Selector监听多个客户端连接。ByteBuffer: 用于在通道和内存之间传输数据。
-
多线程多 I/O(使用多路复用技术)
Selector: 同上,用于管理多个I/O通道。SelectableChannel: 同上,支持多路复用的通道。SocketChannel: 同上,支持非阻塞I/O。ServerSocketChannel: 同上,用于处理多客户端连接。ByteBuffer: 同上,用于数据传输。ExecutorService: 提供线程池管理。
3. 异步 I/O
AsynchronousSocketChannel: 支持非阻塞的网络Socket操作。AsynchronousServerSocketChannel: 支持异步服务器Socket操作。AsynchronousFileChannel: 支持异步文件I/O操作。
9.在设计高并发系统时,你会如何选择合适的IO模型(如同步/异步,阻塞/非阻塞)?
-
同步/异步:
- 同步:进行 I/O 操作时,应用程序会被阻塞直到操作完成。
- 异步:应用程序发起一个 I/O 操作后立即返回,继续执行后续代码,I/O 操作完成后,通过回调、事件、Promise 或 Future 等机制得到通知
-
阻塞/非阻塞:
- 阻塞:导致请求它的线程在 I/O 操作完成前被挂起
- 非阻塞:许线程发起 I/O 操作后不必等待其完成就可以进行其他工作,通过轮询或事件通知来获取 I/O 操作的结果
10.请谈谈在使用Java NIO进行网络编程时,如何处理半关闭的连接?
11.Linux中select、poll、epoll的区别是什么?
FD:文件描述符,由内核分配,表示一个打开的文件、socket、管道或其他 IO资源,通过这些标识符,程序才可以使用系统调用方法进行 IO操作。
水平触发:持续通知,简单易用,适合一般情况。
边缘触发:只在状态变化时通知,效率更高,适合高并发环境。这三个都是用来实现 IO 多路复用的机制,允许程序同时监控多个 IO流,并且在其流的就绪态进行一些读写操作
- Select:
- I/O事件发生,不知道哪一个流,只能通过轮询查找所有流,找出能够读写数据的流,具有O(n)复杂度。
- 最大连接数过小,默认只有1024(可通过内核修改),随着连接数增加,性能显著下降。
- 使用数组来存储fd
- 支持水平触发
- poll:
- poll本质上和select没有区别,poll使用的是链表存储fd
- 最大连接数无限制,基于链表存储
- 时间复杂度:O(n)
- 支持水平触发
- epoll:
- 时间复杂度为:O(1)
- 使用红黑树(存储和管理注册的fd)和就绪列表(存储当前活跃的fd,当状态一改变存储到该列表)
- 使用事件驱动机制,只通知活跃的fd,避免了无差别轮询
- 支持边缘触发和水平触发.
12.C10K问题是什么?
- C10K :10K个并发连接问题
- 解决方案:
- 每一个连接分配一个进程/线程(资源开销大)
- 同一个线程/进程同时处理多个连接(IO多路复用)
- 循环逐个处理各个连接,每个连接对应一个 socket(当某个连接数据不ready,这个应用阻塞)
- 使用select进行监视(管理fd存在上限,不知道连接是哪一个流,得遍历所有连接,时间复杂度为O(n))
- 使用poll进行监视(使用链表结构,管理fd无限,但还是得遍历所有fd,时间复杂度为O(n))
- 使用epoll进行监视(结构采用红黑树+就绪队列,活跃的fd(资源状态变换)放于就绪队列,时间复杂度为O(1),也叫Reactor、事件驱动、事件轮询(EventLoop))
- 使用libevent库,跨平台封装了底层平台调用,实现统一的接口。
上面内容实现其实也是有对应的瓶颈,当连接数多以及大量都处于ready状态怎么处理?
消息队列、熔断、限流、缓存处理、分布式资源、集群部署协程也叫做用户态进程/用户态线程。区别就在于:进程/线程是操作系统充当了EventLoop调度,而协程是应用程序自己用Epoll进行调度,允许在执行过程中挂起和恢复。
协程的优点是:它比系统线程开销小。其缺点是:如果其中一个协程中有密集计算,其他的协程就不运行了。
13.你平时怎么实现这个异步编程?
-
Future:
- 作为一个存储器,存储了call()的结果
- 配合实现callable的类和线程池的submit方法返回Future对象
- 常用的方法:
- get():调用get()的线程会被阻塞,直返回结果
get(long timeout, TimeUnit unit):有超时的获取cancel():取消任务的执行- Future.cancel(true)适用于任务能够处理interrupt
- Future.cancel(false)仅仅用于避免启动尚未启动的任务
isDone():判断线程是否执行完毕isCancelled():判断是否被取消
- FutureTask:
- 实现了Runnable接口和Future接口;
- 使用时直接可以用来封装一个可异步执行的任务,然后通过线程或者线程池启动该异步任务;
- 可使用的Future的方法去操作这个任务;
-
CompletableFuture:
-
创建异步任务:
-
supplyAsync是创建带有返回值的异步任务
- 使用默认的线程池;
- 使用自定义线程池;
- 获取结果的方法:get、join、getNow、get(time) 都是获取返回结果,但是抛出异常不一样
-
runAsync是创建没有返回值的异步任务
- 使用默认的线程池;
- 使用自定义线程池;
-
-
异步回调:表示某个任务执行完成后执行的动作(Async的区别在于是否子任务在同一个线程执行)
-
返回内容不含异常:
-
thenApply和thenApplyAsync
- thenApply 会将该任务的执行结果即方法返回值作为入参传递到回调方法中,带有返回值
-
thenAccept和thenAcceptAsync
- thenAccep 会将该任务的执行结果即方法返回值作为入参传递到回调方法中,无返回值
-
thenRun和thenRunAsync
- thenRun 无入参,无返回值
-
-
返回内容含有异常
-
whenComplete和whenCompleteAsync
- 会将执行结果或者执行期间抛出的异常传递给回调方法,如果是正常执行则异常为null,回调方法对应的CompletableFuture的result和该任务一致,如果该任务正常执行,则get方法返回执行结果,如果是执行异常,则get方法抛出异常。
- 回调方法无返回值
-
handle和handleAsync
- 跟whenComplete基本一致,区别在于handle的回调方法有返回值
-
-
-
多任务组合处理:
-
thenCombine、thenAcceptBoth 和runAfterBoth:将两个CompletableFuture组合起来处理,只有两个任务都正常完成时,才进行下阶段任务
- thenCombine会将两个任务的执行结果作为所提供函数的参数,且该方法有返回值;
- thenAcceptBoth同样将两个任务的执行结果作为方法入参,但是无返回值;
- runAfterBoth没有入参,也没有返回值。注意两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果
-
applyToEither、acceptEither和runAfterEither:当有一个任务正常完成时,就会进行下阶段任务
- applyToEither会将已经完成任务的执行结果作为所提供函数的参数,且该方法有返回值;
- acceptEither同样将已经完成任务的执行结果作为方法入参,但是无返回值;
- runAfterEither没有入参,也没有返回值
-
allOf / anyOf
-
allOf:CompletableFuture是多个任务都执行完成后才会执行,只有有一个任务执行异常,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回null,如果失败了的话直接抛出异常。
-
anyOf :CompletableFuture是多个任务只要有一个任务执行完成,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回执行完成任务的结果,如果失败了的话就直接抛出异常。
-
-
-
14.有没有使用StreamAPI
- 本质类似于for循环对同一种元素对象进行操作,其中里面封装了filter,map,sort,limit,distinct方法,然后调用collet,count,foreach方法对对象进行处理。
- 相对于一个对集合工具的说法
15.OOM泄漏问题有哪些?
- 静态对象过多,静态资源的生命周期与应用的程序相同,如果静态资源被持有,难以被GC回收
- 单例模式下对象被持有无法被及时释放,很难被GC回收;
- 数据库、IO、Socket等连接资源没有被及时关闭;
- Threadlocal引用没有被及时清理
16.什么情况下出现栈溢出?
- 栈溢出发生在程序调用栈的深度超过 JVM 允许的最大深度时。
- 栈溢出的本质是因为线程的栈空间不足,导致无法再为新的栈帧分配内存。
637

被折叠的 条评论
为什么被折叠?



