目录

刘同学的面经(2025-6)

目录

京东零售-Java

具体说说jdk1.7和1.8的hashmap的线程不安全都有什么问题

JDK 1.7 HashMap的线程不安全问题 在JDK 1.7中,HashMap的线程不安全问题主要体现在扩容过程中。当HashMap进行扩容时,如果多个线程同时操作同一个HashMap,可能会导致以下问题:

  1. 死循环:

当HashMap触发扩容时,如果两个线程A和B同时操作同一个链表,线程A可能在执行扩容函数transfer的过程中被挂起,而线程B则可能在此期间完成数据迁移。 当线程A恢复执行时,由于链表结构已经在线程B的操作下发生改变,线程A可能会按照旧的链表结构进行插入操作,从而导致链表形成环形结构。 这种环形链表结构会导致在后续访问HashMap时出现死循环。

  1. 数据丢失:

在扩容过程中,如果链表形成环形结构,可能会导致某些元素在扩容后无法被正确访问,从而造成数据丢失。 此外,由于扩容操作涉及多个步骤和状态变化,如果线程在扩容过程中被中断或挂起,也可能导致数据不一致或丢失。 JDK 1.8 HashMap的线程不安全问题 在JDK 1.8中,HashMap对底层数据结构进行了优化,引入了红黑树来优化链表过长的问题。然而,线程不安全问题仍然存在,但表现形式有所不同:

  1. 数据覆盖:

当多个线程同时执行put操作时,如果两个线程插入的键值对的哈希值相同(即发生哈希碰撞),则可能会导致数据覆盖。 具体来说,如果一个线程A在判断哈希值位置为null后还未写入数据时被挂起,而另一个线程B在该位置插入了数据并成功完成put操作,那么当线程A恢复执行并尝试写入数据时,就会覆盖线程B已经插入的数据。

  1. size字段不一致:

HashMap中的size字段用于记录当前存储的键值对数量。在多线程环境下,多个线程同时操作put方法可能会导致size值不一致。 例如,两个线程同时增加size的值,可能会导致size的值大于实际存储的键值对数量,进而引发数据覆盖或其他不一致问题。 解决方案 为了避免HashMap的线程不安全问题,可以采取以下解决方案:

  1. 使用Collections.synchronizedMap:

将HashMap包装成线程安全的Map。这通过在HashMap的所有方法上添加synchronized关键字来实现线程同步。

  1. 使用ConcurrentHashMap:

ConcurrentHashMap是JDK提供的线程安全的HashMap实现。它采用分段锁或局部锁等机制来降低锁粒度,提高并发性能。

  1. 手动加锁:

在使用HashMap的地方手动加锁。这可以通过使用synchronized关键字或其他同步机制来保护共享资源。

​ 综上所述,JDK 1.7和JDK 1.8中的HashMap都存在线程不安全问题。在JDK 1.7中,问题主要体现在扩容过程中的死循环和数据丢失;而在JDK 1.8中,问题则主要表现为数据覆盖和size字段不一致。为了避免这些问题,可以使用线程安全的替代方案如ConcurrentHashMap或采取手动加锁等措施。

hashmap链表转化成红黑树的过程以及红黑树转化成链表的过程

链表转化成红黑树的过程

链表长度阈值:当单个桶中的链表长度达到8时,该链表会被转换为红黑树。 最小树化容量:HashMap的总容量(桶数组大小)必须至少为64。如果容量小于64,即使链表长度达到8,也不会进行树化,而是会选择扩容。

红黑树转化成链表

树形化的红黑树节点数量小于6:当红黑树节点的数量减少到6或更少时,红黑树会被转换回链表。这是因为在少量节点的情况下,链表的插入和删除操作比红黑树更高效。

最小树化容量:这是一个辅助条件,用于确保只有在HashMap的容量(桶数组大小)足够大时,才会执行链表到红黑树的转换和反转换。默认情况下,这个值是64。但是,转回链表的主要依据还是节点数量。

hashmap扩容机制原理

扩容原理

当HashMap中的元素数量超过容量与加载因子的乘积时,HashMap会进行扩容。扩容是通过创建一个新的Node数组,其容量是原数组的两倍,然后将原数组中的所有元素重新散列到新数组中。

在JDK1.7和JDK1.8中,HashMap的扩容机制有所不同。在JDK1.7中,扩容涉及重新计算每个元素的存储位置,并使用头插法将链表中的元素逆序插入到新数组中。这种方法可能会导致链表中的环形结构,在多线程环境下可能会引起死循环。

而在JDK1.8中,扩容过程中使用了尾插法,保持了链表的顺序,减少了环形链表的风险。此外,JDK1.8引入了红黑树,当链表长度超过一定阈值(默认为8)时,链表会转换为红黑树,以提高搜索效率。

扩容过程

当HashMap中的元素数量超过容量与加载因子的乘积时,HashMap会进行扩容。扩容是通过创建一个新的Node数组,其容量是原数组的两倍,然后将原数组中的所有元素重新散列到新数组中。

在JDK1.7和JDK1.8中,HashMap的扩容机制有所不同。在JDK1.7中,扩容涉及重新计算每个元素的存储位置,并使用头插法将链表中的元素逆序插入到新数组中。这种方法可能会导致链表中的环形结构,在多线程环境下可能会引起死循环。

而在JDK1.8中,扩容过程中使用了尾插法,保持了链表的顺序,减少了环形链表的风险。此外,JDK1.8引入了红黑树,当链表长度超过一定阈值(默认为8)时,链表会转换为红黑树,以提高搜索效率。

线程创建和cpu交互过程,线程的状态

线程的创建和CPU交互过程主要包括以下几个步骤:

  1. 创建线程对象‌:可以通过继承Thread类或实现Runnable接口来创建线程对象。继承Thread类时,需要重写run()方法;实现Runnable接口时,需要实现run()方法,并通过Thread类的构造方法创建线程对象‌12。
  2. 调用start()方法‌:通过调用线程对象的start()方法,线程进入就绪状态,等待CPU调度。start()方法会启动新线程,并调用run()方法‌12。
  3. CPU调度‌:当线程获得CPU时间片时,线程进入运行状态,开始执行其任务。如果线程执行完毕或时间片用完,线程会回到就绪状态,等待下一次调度‌34。
  4. 状态转换‌:线程的状态包括新建、就绪、运行、阻塞和终止。状态之间的转换如下:
    • 新建‌:线程被创建但未启动。
    • 就绪‌:已启动,等待CPU调度。
    • 运行‌:正在CPU执行。
    • 阻塞‌:等待I/O完成、锁释放或其他条件满足。
    • 终止‌:执行完毕或异常退出‌34。

线程的状态及其定义‌:

  • ‌**新建(New)**‌:线程被创建但未启动。
  • ‌**就绪(Runnable)**‌:已启动,等待CPU调度。
  • ‌**运行(Running)**‌:正在CPU执行。
  • ‌**阻塞(Blocked/Waiting)**‌:等待I/O完成、锁释放或其他条件满足。
  • ‌**终止(Terminated)**‌:执行完毕或异常退出‌34。

通过这些步骤和状态转换,线程与CPU进行有效的交互,完成任务的执行。

线程池参数怎么设置的,为什么

  1. ‌**核心线程数(corePoolSize)**‌:核心线程数是指线程池中常驻的线程数量。对于CPU密集型任务,核心线程数通常设置为CPU核心数+1,因为计算过程中CPU一直处于高负载状态,过多的线程会导致线程上下文切换,降低性能。对于IO密集型任务,核心线程数可以设置为CPU核心数的2倍或更多,因为线程在I/O操作期间会被阻塞,需要更多的线程来提高CPU利用率‌12。
  2. ‌**最大线程数(maximumPoolSize)**‌:最大线程数是线程池允许创建的最大线程数量。当任务队列已满且核心线程数已达到最大值时,线程池会创建新的线程直到达到最大线程数。最大线程数的设置需要根据业务需求和服务器资源来综合考虑‌13。
  3. ‌**任务队列(workQueue)**‌:任务队列用于存放待执行的任务。对于需要快速响应的应用,可以使用无界队列(如LinkedBlockingQueue)来避免任务积压导致的延迟;对于需要控制并发数的应用,可以使用有界队列(如ArrayBlockingQueue)来限制队列长度,避免内存溢出‌23。
  4. ‌**线程空闲存活时间(keepAliveTime)**‌:当线程池中的线程数量超过核心线程数时,多余的空闲线程在等待新任务到来之前会保持存活一段时间。这段时间过后,线程会被终止。设置合理的空闲存活时间可以减少资源浪费‌3。
  5. ‌**拒绝策略(RejectedExecutionHandler)**‌:当线程池已满且任务队列也已满时,新任务会被拒绝。常见的拒绝策略包括:
    • AbortPolicy‌:直接抛出异常,终止任务。
    • CallerRunsPolicy‌:在新任务所在的线程中直接执行任务。
    • DiscardPolicy‌:默默丢弃任务,不作处理。
    • DiscardOldestPolicy‌:丢弃队列中最老的未处理任务,然后提交当前任务‌3。

设置这些参数的原因‌是为了优化系统性能和资源利用:

  • 核心线程数和最大线程数的合理设置‌可以避免过多的线程创建和销毁,减少系统开销,提高系统稳定性。
  • 任务队列的选择‌可以根据业务需求控制任务的积压情况,避免因任务积压导致的系统延迟。
  • 拒绝策略的选择‌可以在系统过载时提供合理的错误处理机制,避免系统崩溃。

mysql insert语句执行过程中 索引的变化

在MySQL数据库中,插入(INSERT)数据时,索引的变化主要体现在两个方面:一是插入操作的效率,二是索引对查询性能的影响。理解这些变化有助于优化数据库性能和查询效率。

1. 插入操作对索引的影响

当你在MySQL中插入数据时,如果表中存在索引(尤其是主键或唯一索引),MySQL需要确保新插入的数据不会违反索引的唯一性约束。这通常涉及以下几个步骤:

  • 查找位置:对于有索引的列,MySQL会先查找该列的索引来确定插入点的位置。
  • 维护索引:在找到插入位置后,MySQL需要更新索引以反映新的数据位置。对于非聚集索引(secondary indexes),这意味着更新索引页中的指针或值。
  • 写入数据:数据被写入数据页,然后是更新索引页。

2. 性能影响

  • 插入速度:对于聚集索引(通常是主键),插入操作相对较快,因为数据和索引是物理上连续存储的。但对于非聚集索引,每次插入都需要更新索引页,这可能会稍微减慢插入速度。
  • 空间使用:频繁的插入操作可能会导致索引页分裂,特别是在使用B-Tree索引时。这会增加磁盘I/O操作,因为需要读取、修改和写入多个页。
  • 碎片化:随着数据的不断插入和删除,索引可能会变得碎片化,这会影响查询性能。定期的优化操作(如OPTIMIZE TABLE)可以帮助减少碎片化。

3. 优化策略

  • 批量插入:尽可能使用批量插入(例如INSERT INTO table_name VALUES (...), (...), ...;)来减少磁盘I/O操作和提高效率。
  • 选择合适的索引:只在确实需要提高查询性能的列上创建索引。避免在经常更新的列上创建过多的索引,因为这会增加写操作的开销。
  • 定期维护:定期使用OPTIMIZE TABLE来减少碎片化,特别是在高更新频率的表上。
  • 考虑分区:对于非常大的表,考虑使用分区来管理数据,这可以帮助提高查询和维护操作的效率。

两个时间段,通过排序剪枝判断是否有重叠的部分

首先,将所有时间段按照开始时间从小到大进行排序。然后,遍历排序后的时间段列表,对于当前时间段,检查其结束时间是否大于前一个时间段的开始时间。如果存在这样的情况,说明当前时间段与前一个时间段有重叠。具体步骤如下:

  1. 排序‌:将所有时间段按照开始时间从小到大进行排序。
  2. 遍历‌:从第一个时间段开始,逐个检查后续时间段。
  3. 剪枝‌:如果当前时间段的开始时间小于等于前一个时间段的结束时间,说明有重叠,返回重叠状态。
  4. 无重叠‌:如果遍历完所有时间段都没有发现重叠,则说明没有重叠。

这种方法利用了排序后的时间段列表中,相邻时间段可能重叠的特性,通过剪枝(即提前终止检查)来优化性能。

字节番茄小说-Java

数据库底层死锁的原因

数据库底层死锁的主要原因‌包括以下四个必要条件:

  • 互斥条件‌:资源必须以独占模式被访问,即一次只能被一个进程或线程占用‌
  • ‌持有并等待条件‌:一个进程持有至少一个资源的同时,等待获取其他资源,而不释放已持有的资源‌
  • 不可剥夺条件‌:已分配给进程的资源不能被强制回收,只能由持有资源的进程主动释放‌
  • 循环等待条件‌:存在一个进程资源的申请序列,使得每个进程都在等待下一个进程所持有的资源,形成循环等待的环路‌

死锁的检测和解决策略 数据库管理系统提供了多种策略来检测和解决死锁:

  • 自动检测并回滚事务‌:当检测到死锁时,系统会自动选择一个事务进行回滚,以解除死锁状态‌
  • 预防策略‌:通过设定事务的锁顺序、避免长时间的事务、限制事务的并发数量等,以减少死锁发生的可能性‌
  • ‌检测和解决策略‌:一旦系统检测到死锁发生,会立即采取措施,如中断一个或多个事务的执行,释放其锁定的资源,从而打破死锁状态‌

/posts/%E5%88%98%E5%90%8C%E5%AD%A6%E7%9A%84%E9%9D%A2%E7%BB%8F2025-6/images/image-20250621233534886.png

redis限流为什么使用lua? 为什么不用指令?

  • 性能和原子性: Lua脚本可以在Redis服务器端原子性地执行多个命令,避免了多次网络通信的开销,提高了性能和原子性。相比之下,用Java或Python实现的限流算法需要多次与Redis进行通信,性能相对较低。

  • 便捷性: Lua脚本可以直接在Redis服务器端执行,无需额外部署其他语言的运行环境,更加灵活和便捷。

  • Redis支持: Redis天然支持Lua脚本,可以直接执行,不需要额外的配置和插件。而如果使用Java或Python,需要额外的库或框架来与Redis进行交互。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
local key = KEYS[1] -- 获取传入lua脚本的第一个keys参数,用作存储令牌数目的键名
local limit = tonumber(ARGV[1]) -- 将传入lua脚本的第一个ARGV参数转换为整数,表示桶的容量
local current = tonumber(redis.call('get', key) or "0")
-- 通过Redis的GET命令获取当前令牌桶中的令牌数量,如果没有获取到则默认为0,并将其转换为整数。

if current + 1 > limit then -- 判断当前令牌桶中的令牌数量加1后是否超过阈值
    return 0 -- 超过表示请求被限流,返回0
else
    redis.call('INCR', key) -- 通过Redis的INCR命令将令牌桶中的数量加1,表示通过了一个请求
    redis.call('EXPIRE', key, ARGV[2]) -- 设置令牌桶的过期时间为ARGV 参数中指定的时间
 
    return 1  -- 返回1,表示通过限流检查
end

redis 是计算密集还是cpu密集,为什么?

Redis 主要是 CPU密集型 ,而不是 IO密集型 ‌。Redis的操作本质上是CPU密集型,而不是IO密集型。这是因为Redis在网络IO上使用 epoll 实现了一个IO多路复用的 reactor模型 ,epoll是非阻塞IO,避免了CPU阻塞在IO上,因此Redis的瓶颈不在于等待IO导致CPU利用率不高,不需要多个线程来屏蔽等待IO执行完成的时间‌。

Redis的架构特点

  1. 单线程架构‌:Redis采用单线程架构,避免了线程切换带来的开销。单线程处理减少了线程争用带来的缓存失效问题,避免了频繁的缓存刷新,从而提升了性能‌。
  2. IO多路复用‌:Redis使用epoll等机制实现非阻塞IO,使得单线程在面对多个客户端连接时仍能保持高效处理‌。
  3. 数据结构和操作‌:Redis支持丰富的数据结构,如string、list、hash、set、zset等。这些数据结构的时间复杂度较高时,可能会导致CPU占用大量时间进行运算,从而表现出CPU密集型的特性‌

redis多路复用原理和多线程的区别

Redis的多路复用(I/O Multiplexing)和多线程(Multi-threading)是两种不同的并发处理机制,它们在Redis中扮演着互补但独立的角色,主要区别如下:

🔄 一、多路复用原理(I/O Multiplexing)

  1. 核心目标‌ 解决单线程下‌高并发连接‌的问题,用单个线程同时监听并处理多个客户端请求36。
  2. 工作原理
    • 通过系统调用(如epollselect)监控多个网络连接的读写事件(如READ/WRITE事件)36。
    • 当某个连接有数据可读/写时,操作系统通知Redis主线程处理对应事件(如读取请求、发送响应),避免线程阻塞等待I/O910。
    • 所有命令执行‌仍由单线程顺序处理,保证原子性15。
  3. 优势
    • 减少线程切换开销,避免多线程锁竞争511。
    • 高效管理数万并发连接(如epoll时间复杂度O(1))69。

⚙️ 二、多线程机制(Redis 6.0+)

  1. 引入目的‌ 解决‌网络I/O吞吐瓶颈‌(尤其是大流量场景),将耗时的网络读写任务并行化24。
  2. 分工设计
    • 主线程‌:单线程执行命令逻辑(保证原子性)并分配I/O任务411。
    • I/O线程组‌:负责socket读写(数据从内核态→用户态拷贝)、请求解析与响应发送12。
    • 后台线程‌:处理异步任务(如持久化、大Key删除)47。
  3. 工作流程‌ 客户端请求 → 主线程通过多路复用接收 → 解析后的请求分发给I/O线程并行读写 → 主线程执行命令 → I/O线程发送响应412。

🔍 三、核心区别总结

特性 多路复用 多线程
作用层面 网络连接管理(事件驱动)36 网络I/O任务并行化24
线程模型 单线程监听事件 主线程 + I/O工作线程组
关键目标 高并发连接处理 提升网络吞吐量
数据安全性 命令执行天然原子性512 命令仍由主线程串行执行,保持原子性1112
版本依赖 所有版本核心机制 Redis 6.0+ 引入411

💎 四、协同关系

  • 多路复用是基础‌:主线程通过多路复用监听连接事件,再决定是否启用I/O线程处理网络数据112。
  • 多线程是优化‌:针对网络I/O的瓶颈(如大流量下的数据拷贝耗时),通过多线程加速,但‌不改变命令执行的单线程本质‌411。

实现concurhashmap 自己写乐观锁和悲观锁怎么实现 cas是怎么实现的

hashMap扩容的过程

hashmap 扩容为什么是两倍

HashMap 扩容时容量加倍(如 16 → 32)的设计核心在于 ‌利用位运算优化性能、保证哈希分布均匀及提升扩容效率‌,具体原因如下:


🔍 一、核心原因:优化索引计算与性能

  1. 位运算取代取模,提升计算效率

    • HashMap 通过 (n-1) & hash 计算索引(n 为数组长度),而非传统的 hash % n
    • ‌**当 n 是 2 的幂次方时(如 16=10000),n-1 的二进制为全 1(如 15=1111)**‌。 此时 (n-1) & hash 等价于 hash % n,但位运算(&)比取模(%)快 10 倍以上。
    • 扩容为 2 倍能保证新容量仍是 2 的幂次方‌,延续位运算优势。

    示例‌:

    1
    2
    3
    
    textCopy Code原容量 n=16 (二进制 10000), n-1=15 (1111)  
    hash=27 (二进制 11011) → 27 & 15 = 1011 (即索引 11)  
    扩容后 n=32 (100000), n-1=31 (11111) → 27 & 31 = 11011 (仍为 27,但实际索引需根据高位判断)
    

⚙️ 二、减少哈希冲突,均匀分布元素

  1. ‌哈希分布更均匀
    • n 为 2 的幂次方时,n-1 的二进制全 1 特性使得哈希值的低位能均匀映射到数组各位置。
    • 若容量非 2 的幂次方(如 15=1110),部分索引永远无法被映射(如末位为 1 的位置),导致哈希冲突概率增加。

🚀 三、优化扩容时元素迁移效率(JDK 1.8+)

  1. 元素新位置只需一次位判断
    • 扩容后(newCap = oldCap « 1),元素的新位置仅有两种可能:
      • 原位不变‌(如原索引 13 → 新索引 13)
      • 原位 + 原容量‌(如原索引 13 → 新索引 13+16=29)。
    • ‌判断逻辑:检查哈希值新增的高位是 0 还是 1(即hash & oldCap):
      • =0 → 索引不变
      • =1 → 索引 = 原索引 + oldCap
  2. 优势
    • 无需重新计算哈希值‌,迁移时间复杂度从 O(n) 降至 O(1)。
    • 只需遍历旧数组,按高位判断即可将元素拆分到新数组的两个位置。

💎 四、为何不采用 1.5 倍或 2.5 倍?

  1. ‌失去位运算优化基础
    • 若扩容 1.5 倍(如 16→24),新容量不再是 2 的幂次方,无法用 (n-1) & hash 替代取模,性能下降。
  2. ‌元素迁移效率降低
    • 非 2 倍扩容时,元素需重新计算哈希值并分配位置,迁移成本显著增加。

📊 关键优势总结

特性 扩容 2 倍的优势 非 2 倍扩容的问题
索引计算 位运算 & 替代取模 %,效率更高 依赖低速取模运算
哈希分布 n-1 全 1 保障均匀映射 部分索引空缺,冲突概率增加
扩容迁移效率 高位判断即可确定新位置,O(1) 复杂度 需重算哈希值,O(n) 复杂度
容量对齐 始终维持 2 的幂次方特性 破坏幂次方特性,后续操作效率降低

💡 ‌结论‌:HashMap 扩容 2 倍是性能(位运算)、空间(均匀分布)、迁移效率(高位判断)三重优化的工程权衡结果。

java nio 原理 - (Java NIO 原理:非阻塞 I/O 的高性能架构)

Java NIO(New I/O)采用‌事件驱动模型‌解决传统阻塞 I/O 的并发瓶颈,核心是通过 ‌**Channel(通道)‌、‌Buffer(缓冲区)‌ 和 ‌Selector(选择器)**‌ 实现高效数据传输。其工作原理如下:


🔄 一、核心组件协同流程

  1. ‌**Channel(通道)**‌

    • 替代传统 InputStream/OutputStream,支持‌双向读写‌(读/写可同时进行)。
    • 类型:SocketChannel(TCP)、DatagramChannel(UDP)、FileChannel(文件)。
    • 非阻塞模式‌:调用 configureBlocking(false) 后,读写操作无数据时立即返回,避免线程阻塞。
  2. ‌**Buffer(缓冲区)**‌

    • 本质是‌内存块数组‌(如 ByteBuffer, IntBuffer),作为数据中转站。

    • ‌工作流程:

      1
      2
      3
      
      javaCopy Codebuffer.flip();     // 切换读模式(写→读)
      buffer.get();      // 读取数据
      buffer.clear();    // 清空复用(读→写)
      
  3. ‌**Selector(选择器)**‌

    • 单线程管理多通道‌:通过系统调用(epoll/kqueue)监听所有注册的 Channel 事件。
    • 事件类型:
      • OP_READ(数据可读)
      • OP_WRITE(通道可写)
      • OP_CONNECT(连接就绪)

⚙ 二、事件驱动工作流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
javaCopy Code// 伪代码示例
Selector selector = Selector.open();
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ); // 注册读事件

while (true) {
    int readyChannels = selector.select();     // 阻塞直到事件就绪
    Set<SelectionKey> keys = selector.selectedKeys();
    for (SelectionKey key : keys) {
        if (key.isReadable()) {
            SocketChannel ch = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            ch.read(buffer);  // 非阻塞读取
            buffer.flip();
            // 处理数据...
        }
        keys.remove(key);
    }
}
  1. 事件注册
    • Channel 向 Selector 注册关注的事件(如 OP_READ)。
  2. 事件监听
    • selector.select() 调用底层 epoll,由操作系统通知就绪事件。
  3. 事件处理
    • 遍历 selectedKeys() 处理就绪 Channel,单线程可处理数千连接。

⚡️ 三、性能飞跃的关键设计

特性 传统阻塞 I/O Java NIO
线程模型 1 连接 = 1 线程(线程切换开销大) 单线程处理多连接(Selector 管理)
I/O 行为 读写阻塞线程直至完成 无数据时立即返回(configureBlocking(false)
数据操作 流式传输(单向) 缓冲区批量读写(双向)
并发能力 数百连接(线程数限制) 数万连接(C10K 问题解决方案)

💡 四、底层机制:多路复用(以 Linux 为例)

  1. ‌**epoll 核心优势**‌
    • 事件通知机制‌:仅返回就绪的 Channel 集合,无需遍历所有连接(时间复杂度 O(1))。
    • 零拷贝支持‌:通过 FileChannel.transferTo() 实现内核态直接传输,避免用户态拷贝。
  2. 与 Redis 多路复用对比
    • 相似点‌:均使用 epoll 实现单线程高并发。
    • 差异点‌:Redis 主线程处理所有逻辑,Java NIO 可将业务逻辑分发给工作线程池。

🚀 五、适用场景

  • 高并发服务器‌:Web 服务器(Netty)、消息中间件(RocketMQ)。
  • 大文件传输‌:零拷贝技术提升吞吐量。
  • 实时通信‌:WebSocket、游戏服务器。

💡 ‌注意事项‌:

  • NIO 编程复杂度高(需处理拆包/粘包、异常恢复)。
  • 推荐使用 ‌Netty‌ 等框架简化开发,其基于 NIO 封装了稳定高性能的 API。

通过 ‌Channel-Buffer-Selector 三角协作‌,Java NIO 在同等资源下实现 10 倍于传统 I/O 的并发能力,成为高性能网络应用的基石。

百度-Java

百度一面

redis的一致性hash

redis lua脚本 如何避免限流失败,过期时间,自动续期

分布式锁 zk redis

zk优缺点

网关 geteway、zuul分布式提高高可用,nginx

分析性能问题思路

找最高点算法 一个for

原地交换算法两个for

链表倒数500,经典双指针闭眼秒。

ip范围的优化 zset redis

百度二面

算法 字符串反转 有多少方法 快速排序

字符串常量池 创建了几个对象 1-2

集合stream解决问题 时间内(filter),金额最大 前五,(limit),映射为一个属性(map)

线程池参数 根据什么配置

如何创建线程 Runnable Callable Thread CompletableFuture 线程池

thredlocal 线程池内容相同下

synchronized 和 ReentrantLock 区别什么时候使用

jvm 垃圾处理器 cms和g1 cms有几个阶段,g1 他们的区别

redis lua 脚本为什么用于限流 nginx

分布式锁两种方式怎么实现的

mysql 执行计划的参数 index性能为什么不好(走了全部索引)

跨域怎么解决 (nginx 反带到允许域上,gateway设置允许域)

Springboot常用的注解

zab和raft区别

滴滴-Java

滴滴一面

java base hasmap扩容为什么是两倍

​ 充分散列 ​ 位移问题向高位移动

jvm cms和g1

redis 为什么速度快

mysql inodb

B+树和b树、和红黑树

acid是什么

怎么实现的持久性

滴滴二面

算法简单 第一列顺序,第二列倒叙排序

自己挖坑提到单调栈 要求实现,说java有,糊弄过去了

pb级别文件排序(多路归并),描述归并排序

mysql select * from tbl where a=1 select * from tbl where b=2 and a=1 select * from tbl where c=3 and b=2 and a=1

abc联合索引 哪个走联合索引 第二个和第三个一定不走吗 第一个什么情况不走索引

redis redis项目做什么 做分布式锁做注意什么 设计方案

高德-Java

sql优化手段 activiti比flowable等框架好在哪

设计一个系统 1高并发,不能重复 幂等问题 2一致性?高可用?吞吐量?不记得了,面完应该直接写的 3熔断限流 重试次数,死信队列,lua脚本限制

消息的库表需要什么字段

京东物流-Java

京东物流一面

加载过程,类加载到静态方法区

在Java中,类的加载过程涉及到Java虚拟机(JVM)的类加载器(Class Loader)机制。这个过程大致可以分为以下几个阶段:

  1. 加载(Loading)

    这一阶段主要是将类的字节码从不同的来源(如文件系统、网络等)加载到JVM中。

    类加载器(如Bootstrap ClassLoader, Extension ClassLoader, System ClassLoader或自定义类加载器)负责查找并加载类的二进制数据。

  2. 链接(Linking)

    • 验证(Verification):确保被加载的类的正确性,例如检查字节码的格式是否正确,是否符合Java语言规范。
    • 准备(Preparation):为类的静态变量分配内存,并设置默认初始值(如int类型变量默认为0,对象引用默认为null)。
    • 解析(Resolution):将类、接口、字段和方法的符号引用转换为直接引用。这一步骤可能在初始化之前完成,也可能在初始化时完成。
  3. 初始化(Initialization)

    执行类中的静态初始化器和静态初始化块。这是Java代码执行的最后一步,此时静态变量被赋予正确的值,静态代码块被执行。

  4. 使用(Using)

    • 程序通过创建类的实例或使用类的静态变量和方法来使用类。
  5. 卸载(Unloading)

    • 当类不再被使用时,JVM会卸载这个类。这通常发生在类的Class对象没有在任何地方被引用时。

值得注意的是,在Java中,静态方法区(也称为方法区或Metaspace,在Java 8及之后版本中称为Metaspace而非永久代PermGen)是用来存储每个类的结构信息,如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容等。它与类的加载和链接阶段密切相关,但不直接涉及初始化过程。

** 静态方法区与类加载过程的关系:**

  • 方法区 主要存储了类的元数据信息,包括类的字段信息、方法信息、常量池等。这些信息在类加载的链接阶段就已经准备好了。
  • 初始化 主要涉及的是执行类的静态代码块和静态变量的初始化,这部分是在加载和链接之后进行的。

因此,可以说静态方法区是存储了类的元数据的地方,而类的初始化过程则是在这些元数据基础上进行的。两者共同构成了Java中类的完整生命周期的一部分。

java线程池底层机制

spring事务失效 的情况

Spring 事务失效是指使用 @Transactional 注解时,事务行为未按预期执行(如未回滚),常见于以下 8 种场景,其失效原理与解决方案如下:


🔧 一、Spring 事务失效的 8 种核心场景

场景 失效原因 解决方案
1. 非 public 方法 Spring 动态代理仅拦截 public 方法,私有/包级方法无法被代理增强事务 将事务方法设为 public
‌**2. 自调用(内部调用)**‌ 类内方法通过 this 调用事务方法,绕过代理对象 通过代理对象调用(如 AopContext.currentProxy())或拆分到不同 Bean
3. 异常处理不当 • 默认仅回滚 RuntimeExceptionError • 捕获异常未抛出,Spring 无法感知 配置 @Transactional(rollbackFor = Exception.class) 或在 catch 中抛出 RuntimeException56
4. 多线程调用 子线程与父线程事务上下文隔离,数据库连接不同 避免多线程操作,或使用分布式事务管理器
5. 数据库引擎不支持 如 MySQL 的 MyISAM 引擎不支持事务 改用 InnoDB 等支持事务的引擎
6. 方法被 final/static 修饰 代理类无法重写 final 方法;static 方法不属于实例,无法被代理 移除 final/static 修饰符
7. 未被 Spring 管理 类未标注 @Component 等注解,Spring 无法创建代理 确保 Bean 被 Spring 容器扫描
8. 错误的事务传播机制 如配置 Propagation.NOT_SUPPORTED(以非事务运行) 调整传播行为(如默认 REQUIRED

⚙️ 二、事务传播机制(Propagation)与事务边界

Spring 通过传播机制控制事务边界,常见类型如下:

传播类型 行为描述 典型场景
REQUIRED‌(默认) 当前有事务则加入,无事务则新建 多数业务场景(如订单扣库存)
REQUIRES_NEW 挂起当前事务,始终新建独立事务(新事务回滚不影响原事务) 日志记录、独立操作(如发送短信)
NESTED 在当前事务内嵌套子事务(子事务回滚不影响主事务,主事务回滚则子事务一同回滚) 复杂业务分步骤执行(如订单拆分支付)
SUPPORTS 有事务则加入,无事务则以非事务方式运行 查询方法兼容有无事务上下文

⚠️ ‌失效案例‌:若方法需独立事务却误配 SUPPORTS,事务将不生效。


💡 三、事务管理方式对比(声明式 vs 编程式)

特性 ‌**声明式事务(@Transactional)**‌ ‌**编程式事务(TransactionTemplate)**‌
实现原理 基于 AOP 动态代理封装事务逻辑 需手动编写事务代码(如 template.execute()
灵活性 注解配置简单,但复杂逻辑受限 精细控制事务边界,适合多步骤非原子操作
适用场景 大多数标准业务场景 需动态判断回滚条件、多数据源混合操作

关键建议‌:

  • 优先用声明式事务,保持代码简洁;
  • 传播机制配置需结合业务语义,避免 NOT_SUPPORTED/NEVER 误用;
  • 多线程或跨微服务调用需引入分布式事务(如 Seata)

start的机制

mysql 最左前匹配

MySQL 的最左前缀匹配(Leftmost Prefix Matching)是 B+树索引的核心特性,指复合索引按照从左到右的顺序进行匹配的规则。

一、最左前缀匹配的本质

当你在 MySQL 中创建复合索引时,例如:

1
2
3
4
5
sqlCopy Code



CREATE INDEX idx_name_age ON users(name, age, gender);

这个索引会按照 name → age → gender 的顺序构建 B+树,查询时必须从最左边的列开始使用才能生效。

二、有效使用最左前缀的查询示例

查询条件 是否使用索引 原因
WHERE name = '张三' ✅ 完全使用 从最左列开始
WHERE name = '李四' AND age = 25 ✅ 完全使用 按顺序使用前两列
WHERE age = 30 ❌ 不使用 缺少最左列
WHERE name = '王五' AND gender = 'M' ⚠️ 部分使用 只用到 name 列

三、特殊情况的处理

  1. LIKE 模糊查询‌:

    1
    2
    3
    4
    5
    
    sqlCopy Code-- 可以使用索引
    WHERE name LIKE '张%'
    
    -- 不能使用索引
    WHERE name LIKE '%张'
    
  2. 范围查询后的列失效‌:

    1
    2
    
    sqlCopy Code-- age 和 gender 无法使用索引
    WHERE name = '张三' AND age > 20 AND gender = 'M'
    

四、优化建议

  1. 将选择性高的列放在索引左侧
  2. 避免在索引列上使用函数或计算
  3. 合理设计查询语句,确保从最左列开始使用

理解最左前缀匹配原理可以帮助你设计更高效的索引结构,提升查询性能。

sql优化 索引

redis分布式通信

复制是三个sync

直接复制,断点复制,宕机复制断点

底层是gossip协议,反熵和流言模式

事务 spring start原理

京东物流二面

低代码架构

流程架构

mq重试

数据量多

压力大的部分

百度搜索-c++

c++ 共享指针 内存分配 new对象过程

操作系统 页缓存 tlb cpu缓存

ai rag底层流程

spring 好在哪 依赖注入、控制反转属于什么设计模式

子序列最大 贪心做的 反转链表 用c++ 内存池 太长没做

飞书-Java

java 抽象类、普通类 、接口的区别

反射的原理 还有什么方式动态实现

用过什么锁 sync、reen、分布式锁 不同情况用什么

redis 项目使用 序列化方式????

争论mq的push和pull

设计定时任务调度+高并发

算法 三数之和