回答重点1)不要滥用 ThreadLocal:
ThreadLocal 适用于需要为每个线程维护独立副本的场景,例如:数据库连接、用户会话、事务上下文、临时缓存等。
对于能通过参数传递的上下文信息,不应该使用 ThreadLocal 来处理,避免设计不合理和代码可读性差的问题。
2)避免内存泄漏:
ThreadLocal 中的 key 是弱引用,但 value 是强引用,因此需要在适当...
回答重点InheritableThreadLocal 是 ThreadLocal 的一个扩展,用于在线程创建时将父线程的 ThreadLocal 变量副本传递给子线程,使得子线程可以访问父线程中设置的本地变量。它解决了 ThreadLocal 无法在子线程中继承父线程本地变量的问题。
工作原理:
当创建子线程时,InheritableThreadLocal 的值会被自动拷贝到子线程中。
子...
回答重点正常情况下使用 ThreadLocal 是没什么问题的,但是如果极端情况下,数据比较多,则可能会出现以下几个问题:
内存泄露问题
ThreadLocal 中的 ThreadLocalMap Hash 冲突用的是线性探测法,效率低
ThreadLocal 主动清理的开销问题
扩展知识内存泄漏问题ThreadLocal 的生命周期和线程的生命周期绑定,线程池中的线程可能被复用,但 T...
我们先来思考下 ThreadLocal 有哪些不够好的地方,如果要改都需要改哪些实现。
ThreadLocal hash 冲突的线性探测法不好,还有 Entry 的弱引用可能会发生内存泄漏,这些都和 ThreadLocalMap 有关,所以需要搞个新的 map 来替换 ThreadLocalMap。
而这个 ThreadLocalMap 又是 Thread 里面的一个成员变量,这么一看 Th...
这玩意是阿里开源的一个组件,原生的 ThreadLocal 不支持在线程池中传递本地变量,所以弄了个这个来实现下这个需求
接下来我们来详细探讨一下
缘由任何一个组件的出现必有其缘由,知其缘由背景才能更深刻地理解它。
我们知道 ThreadLocal 的出现就是为了本地化线程资源,防止不必要的多线程之间的竞争。
在有些场景,当父线程 new 一个子线程的时候,希望把它的 ThreadLoca...
看起来 Thread.sleep(0) 很奇怪,让线程睡眠 0 毫秒?那不是等于没睡眠吗?
是的,确实没有睡眠,但是调用了 Thread.sleep(0) 当前的线程会暂时出让 CPU ,这使得 CPU 的资源短暂的空闲出来别的线程有机会得到 CPU 资源。
所以,在一些大循环场景,如果害怕这段逻辑一直占用 CPU 资源,则可以调用 Thread.sleep(0) 让别的线程有机会使用 CP...
回答重点这三个方法都属于 Object 内种定义的方法,主要用于线程之间的通信和同步,且需要在 synchronized 修饰的方法或同步块中使用。
wait() 使得当前线程进入等待状态,且会释放锁。
notify() 会顺序(在 hotspot 的实现中)唤醒一个调用 wait 后等待的线程。
notifyAll 会唤醒所有调用 wait 等待的线程。
扩展知识在 Object 源码中 ...
重点回答默认情况下,线程池不会直接报告哪个线程发生了异常,但是可以采取以下几种方法:
1)**自定义线程池的 ThreadFactory**:
通过自定义 ThreadFactory,为每个线程设置一个异常处理器(UncaughtExceptionHandler),在其中记录发生异常的线程信息。
2)**使用 Future**:
提交任务时使用 submit() 方法,而不是 exec...
重点回答shutdown() 和 shutdownNow() 都用于关闭线程池,但工作方式有所不同:
1)**shutdown()**:
启动线程池的平滑关闭。它不再接受新的任务,但会继续执行已经提交的任务(包括在队列中的任务)。
线程池会进入 SHUTDOWN 状态,所有已执行和正在执行的任务都会继续完成,只有所有任务完成后,线程池才会完全终止。
2)**shutdownNow()**:
...
回答重点Thread.sleep() 和 Thread.yield() 都是用于控制线程行为的两个方法。
**Thread.sleep()**:
使当前线程进入休眠状态(TIMED_WAITING 状态),暂停执行指定的时间(以毫秒为单位)。在休眠期间,线程不会占用 CPU 时间片。休眠结束后,线程会尝试重新获取 CPU 时间片,进入可运行状态。
休眠时间取决于操作系统的计时器精度,可能会...
回答重点常见有以下五种方式创建使用多线程:
1)实现 Runnable 接口:
实现 Runnable 接口的 run() 方法,使用 Thread 类的构造函数传入 Runnable 对象,调用 start() 方法启动线程。
例子:Thread thread = new Thread(new MyRunnable()); thread.start();
2)继承 Thread 类:
...
回答重点可以动态修改的。Java 的 ThreadPoolExecutor 提供了动态调整核心线程数和最大线程数的方法。
1)修改核心线程数的方法:
使用 ThreadPoolExecutor.setCorePoolSize(int corePoolSize) 方法可以动态修改核心线程数。corePoolSize 参数代表线程池中的核心线程数,当池中线程数量少于核心线程数时,会创建新的线程...
回答重点1)**使用 Thread.join()**:
主线程通过调用 join() 方法等待子线程执行完毕。子线程正常结束,说明执行成功,若抛出异常则需要捕获处理。
2)**使用 Callable 和 Future**:
通过 Callable 创建可返回结果的任务,并通过 Future.get() 获取子线程的执行结果或捕获异常。Future.get() 会阻塞直到任务完成,若任务...
回答重点线程同步是指在多线程环境下,为了避免多个线程对共享资源进行同时访问,从而引发数据不一致或其他问题的一种机制。它通过对关键代码段加锁,使得同一时刻只有一个线程能够访问共享资源。
当多个线程共享同一资源(如变量、对象或文件)时,若没有同步机制,可能会导致竞态条件,即线程对共享资源的操作是非原子性的,多个线程之间可能会同时修改数据,导致结果不符合预期。
扩展知识Java 中常见的同步方式s...
回答重点Netty 是高性能 Java 网络通信的底层框架,它使用异步、事件驱动等架构,解决了传统 Java 网络编程中的一些复杂问题:
传统BIO阻塞瓶颈:通过NIO多路复用实现单线程万级连接,避免线程爆炸
原生NIO开发复杂度:封装Selector/Channel/Buffer,提供Pipeline责任链式API
网络编程常见痛点:自带TCP粘包拆包、心跳检测、内存...
回答重点Netty 的应用场景主要有以下几个:
许多框架底层通信的实现,比如说 RocketMQ、Dubbo、Elasticsearch、Cassandra 等,底层都使用到了 Netty。
游戏行业,在游戏服务器开发中,Netty 用于处理大量并发的游戏客户端连接,提供低延迟的网络通信能力
实现一个通讯系统,比如聊天室、IM 等,处理高并发的实时消息传输。
物联网即 IOT 场景,Net...
回答重点Reactor 是服务端在网络编程时的一个编程模式,主要由一个基于 Selector (底层是 select/poll/epoll)的死循环线程,也称为 Reactor 线程。
基于事件驱动,将 I/O 操作抽象成不同的事件,每个事件都配置对应的回调函数,由 Selector 监听连接上事件的发生,再进行分发调用相应的回调函数进行事件的处理。
React...
回答重点
前置知识:零拷贝的基本理念
避免在用户态和内核态之间拷贝数据,从而降低 CPU 占用和内存带宽的消耗。
除了系统层面的零拷贝,Netty 还强调在 buffer 之间避免不必要的数据拷贝。
Netty 中的零拷贝包括以下四个方面:
零拷贝机制
描述
FileRegion 接口
利用操作系统的 sendfile 系统调用,将文件数据直接从磁盘传输到网络接口,无需...
回答重点Netty 主要采用了 Reactor 模型,且通过细化线程池管理来处理不同的 I/O 任务。
Netty 的线程模型可以分为三个部分:
Boss 线程组:负责接收客户端的连接请求。
Worker 线程组:负责处理 I/O 读写操作。
业务线程组(可选):负责处理具体的业务逻辑。
具体工作机制1)Boss 线程组:
Boss 线程组中的线程(通常是一个)负责监...
回答重点因为原生的 NIO 存在一些问题:
1)原生 NIO 接口较多,能支持更精细化的调用,但是对于通常的使用而言过于复杂。就好下面这张图,对于已经深耕的专业人员来说,可能极好,但对于大部分人员使用来说太复杂了。
所以如果用原生 NIO 开发的话,需要进行二次封装,开发效率不高,且原生 NIO 对开发者要求较高,不好开发。
2)原生的 NIO 存在一些 Bug,最让人熟悉的就是 Sel...
回答重点Netty 性能高的主要原因如下:
非阻塞 I/O 模型:Netty 底层使用了 NIO 非阻塞模型,并且利用I/O 多路复用,通过 Selector 监听多个 Channel 的 IO 事件,使得系统资源得到了充分利用,减少了线程开销。
高效的内存操作与内存池设计:ByteBuf 提供了丰富的功能,如动态扩展、复合缓冲区等,能高效地进行内存操作,并使用内存池技...
回答重点因为TCP 协议是面向字节流的,数据在传输过程中没有明确的边界,所以会发生粘包和半包问题。
1)粘包问题:
粘包指的是多个应用层的数据包在传输过程中被合并成一个 TCP 数据包,导致接收方无法区分各个独立的数据包。
2)半包问题:
半包指的是一个应用层的数据包在传输过程中被拆分成多个 TCP 数据包,接收方需要多次读取才能获取完整的数据。
常见解决粘包与半包问题有三个方案:
固定长...
回答重点Netty 提供了丰富的自带解码器为我们解决粘包和拆包的问题,也可以让我们自定义序列化解码器。
Netty 自带的解码器
DelimiterBasedFrameDecoder:分隔符解码器,使用特定分隔符来分割消息。
FixedLengthFrameDecoder:固定长度的解码器,可以按照指定长度对消息进行拆包,如果长度不够的话,可以使用空格进行补全,适用于每个消息长度固定的场景。...
回答重点Netty 作为许多中间件的底层网络通讯框架,内部设计使用了很多的设计模式:
1)策略模式
例如,Netty 提供了多种 EventLoopGroup 实现(如 NioEventLoopGroup、EpollEventLoopGroup),可以根据不同的操作系统和需求选择不同的实现。
2)单例模式
我们在面试鸭设计模式题库中提到池化设计经常就需要用到单例模式,Netty 的 Pool...
回答重点Netty 实际上并没有解决 JDK 原生 NIO 中空轮询 bug,而是通过其他途径绕开了这个错误。
具体操作如下:
1)统计空轮询次数:Netty 通过 selectCnt 计数器来统计连续空轮询的次数。每次执行 Selector.select() 方法后,如果发现没有 I/O 事件,selectCnt 就会递增。
2)设置阈值:Netty 定义了一个阈值 SELECT...
回答重点在 Netty 中,Channel 表示一个网络连接,抽象了底层的网络操作,提供绑定、连接、读写和关闭等操作,是网络 I/O 操作的核心抽象。
ChannelHandlerContext 是 Netty 中用来在 ChannelPipeline 中传递数据和处理事件的上下文对象,它连接 ChannelHandler 和 ChannelPipeline,用于在 Channel...
回答重点Java 对象在虚拟机中的存储由以下几个部分组成:
对象头(Header):包含对象的元信息和运行时数据。主要由以下三部分构成:
Mark Word:用于存储运行时数据,例如对象的哈希码(HashCode)、GC 标记信息、锁状态标志等。它是一个多功能字段,会根据对象的状态动态变化。
类型指针(Class Pointer):指向对象对应的类的元数据,用于确定该对象的类型。
数...
回答重点JIT(Just-In-Time)编译后的机器码通常存放在 Code Cache(代码缓存区,不在堆内) 中。
JVM 提供参数用于调整 Code Cache 的大小和行为:
-XX:InitialCodeCacheSize:初始大小。
-XX:ReservedCodeCacheSize:最大大小。
-XX:+PrintCodeCache:打印 Code Cache 信息。
扩展...
回答重点Java 程序的执行流程经历了从编译到字节码的生成,再到类加载和 JIT 编译的过程,最终在 JVM 中执行。并且在程序运行过程中,JVM 负责内存管理、垃圾回收和线程调度等工作。
主要流程如下:
源代码:编写 .java 文件。
编译:使用 javac 编译器生成 .class 字节码文件。
类加载:JVM 的类加载器加载 .class 文件到内存中。
解释执行:JVM 将字节码...
回答重点可以从以下四个方面来回答:
从内存管理机制来看:
G1 将整个堆划分为多个大小相等的Region(默认最多2048个),物理上不再严格区分新生代和老年代,而是根据回收价值动态调整各区域的回收。这样避免了CMS因固定分代导致的内存碎片问题,提升了内存利用率。
从回收算法来看:
CMS 采用标记-清除算法,容易产生内存碎片,可能导致Full GC提前触发
而 G1 基于标记-整理算...