20260227 CJT面试题

1 阅读7分钟

问题 1:介绍一下 HashMap

答案:HashMap 是 Java 中基于哈希表实现的 Map 接口实现类,用于存储键值对,核心特性如下:

  1. 底层结构(JDK1.8):数组(哈希桶)+ 链表(解决哈希冲突)+ 红黑树(链表长度≥8 且数组长度≥64 时转换);
  2. 核心规则:允许 null 键(哈希值固定为 0)和 null 值,无序,非线程安全;
  3. 哈希计算:通过 key.hashCode() ^ (key.hashCode() >>> 16) 混合高低位,数组下标 = 哈希值 & (数组长度 - 1);
  4. 扩容机制:初始容量 16,负载因子 0.75,容量达阈值时扩容为 2 倍,JDK1.8 扩容无需重新计算哈希,仅按位判断位置;
  5. 遍历特性:快速失败(fail-fast),遍历中修改结构会抛出 ConcurrentModificationException

问题 2:HashMap 是不是线程安全的?

答案:不是线程安全的。

  1. 线程不安全场景:

    • 多线程扩容时,JDK1.7 头插法易形成环形链表导致死循环,JDK1.8 虽解决死循环,但仍会出现值覆盖、size 计数不准;
    • 多线程 put() 会覆盖彼此写入的值;
    • 遍历过程中修改数据触发快速失败。
  2. 线程安全替代方案:

    • ConcurrentHashMap(JDK1.7 分段锁,JDK1.8 CAS+Synchronized,效率高);
    • Hashtable(全表加锁,效率低);
    • Collections.synchronizedMap(new HashMap<>())(全局锁,简单场景可用)。

问题 3:volatile 描述下,如何保证可见性的?

答案:volatile 是 Java 轻量级同步机制,保证变量可见性(多线程间变量值实时同步),核心依赖内存屏障 + CPU 缓存一致性协议(MESI)

  1. 写操作:对 volatile 变量写时,JVM 加写内存屏障,强制将变量从 CPU 缓存刷回主内存;
  2. 读操作:对 volatile 变量读时,JVM 加读内存屏障,强制从主内存加载变量,清空 CPU 缓存旧值;
  3. MESI 协议:多核 CPU 中,某核心修改主内存变量后,通知其他核心失效该变量缓存,确保所有核心读取最新值。补充:volatile 不保证原子性(如 i++ 仍需加锁 / AtomicInteger),仅保证可见性和禁止指令重排序。

问题 4:RabbitMQ 如何保证消息不丢失?

答案:核心是 “生产端 + 服务端 + 消费端 + 集群” 四层防护,确保消息全链路不丢失:

  1. 生产端:开启生产者确认(Publisher Confirm),异步监听确认结果,失败则重试 / 记录日志;
  2. 服务端:交换机、队列、消息均开启持久化(durable=true),避免服务宕机丢失消息;
  3. 消费端:关闭自动确认(autoAck=false),业务处理完成后手动 basicAck(),失败则 basicNack() 转入死信队列;
  4. 集群层:开启镜像队列(ha-mode=all),所有节点同步队列数据,避免单节点宕机丢失。

问题 5:如何优雅的关闭线程池?

答案:核心原则:先拒绝新任务 → 等待已提交任务执行完成 → 超时强制关闭,避免任务丢失,步骤如下:

  1. 调用 shutdown():拒绝新任务,等待已提交(含队列中)任务执行完成;
  2. 调用 awaitTermination(timeout, unit):阻塞等待线程池关闭,超时返回 false;
  3. 超时未关闭则调用 shutdownNow():中断正在执行的线程,返回未执行任务列表;
  4. 捕获 InterruptedException:若线程被中断,再次调用 shutdownNow(),并恢复中断状态。核心代码示例:

java运行

executor.shutdown();
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
    executor.shutdownNow();
    if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
        System.err.println("线程池关闭失败");
    }
}

问题 6:介绍下 BIO / NIO

答案

特性BIO(同步阻塞 IO)NIO(同步非阻塞 IO)
核心模型阻塞式,一个连接一个线程非阻塞式,Reactor 事件驱动模型
关键组件Socket、ServerSocketChannel、Buffer、Selector
连接处理线程阻塞等待数据,线程池易打满Selector 监听多 Channel 事件,单线程处理多连接
读写方式流式读写(一次读一个字节)缓冲区批量读写
性能高并发下线程切换开销大低开销,支持百万级连接
适用场景连接数少、短连接(如简单 HTTP)连接数多、长连接(如 Netty、Redis)

问题 7:Spring Cloud Gateway 底层用的是什么模型?

答案:Spring Cloud Gateway 底层基于 Netty + Reactor 响应式模型(NIO)

  1. 网络层:基于 Netty 的 NIO 模型(Epoll 多路复用),非阻塞 IO 处理网络请求;
  2. 编程层:基于 Reactor 3(Flux/Mono)响应式编程,异步非阻塞处理请求链路;
  3. 核心优势:相比 Zuul 1.x(Servlet/BIO),性能提升 3-5 倍,支持动态路由、流式处理。

问题 8:一个请求走到网关,操作系统经历了什么?

答案:以 Spring Cloud Gateway(Netty)为例,操作系统层面流程:

  1. TCP 三次握手:客户端发起 SYN → 网关内核 TCP 栈回应 SYN+ACK → 客户端 ACK,建立连接;
  2. Socket 监听:网关绑定端口,操作系统创建 listen socket 处于 LISTEN 状态;
  3. 连接接收:内核将新连接从半连接队列移到全连接队列,Netty 通过 accept() 获取连接;
  4. IO 多路复用:Netty 的 Selector 调用 epoll_ctl 注册 Channel 读 / 写事件;
  5. 数据读取:网卡数据 → 内核缓冲区 → 用户态(Netty ByteBuf);
  6. 请求处理:Netty EventLoop 处理请求,经过 Filter 链、路由转发;
  7. 响应返回:数据从用户态 → 内核缓冲区 → 网卡发送给客户端;
  8. 可选 TCP 四次挥手:连接关闭时交互 FIN/ACK 报文。

问题 9:分库分表以后非分片的 key 如何保证查询效率?

答案:核心是避免 “全库全表扫描”,优化方案按优先级排序:

  1. 二级索引表:新建 “非分片键 → 分片键” 映射表(如 order_id → user_id),先查映射表拿到分片键,再路由查询;
  2. 全局索引:用 Elasticsearch 构建全量数据索引,非分片键查询先查 ES 拿到分片键,再查数据库;
  3. 广播表:非分片键为静态小表(如币种、地区),每个分库存储一份,直接查询;
  4. 二次查询:先按非分片键范围过滤,再按分片键路由(如按时间查近 7 天,再按 user_id 路由);
  5. 中间件优化:用 Sharding-JDBC 的 HintManager 手动指定分片键,强制路由。

问题 10:SQL 优化

答案:SQL 优化核心围绕 “减少 IO、减少扫描行数”,核心措施:

  1. 索引优化:

    • 建覆盖索引避免回表;
    • 联合索引遵循最左前缀原则;
    • 禁止在索引列做函数 / 运算(如 DATE(create_time) = '2024-01-01');
  2. 语句优化:

    • 禁用 SELECT *,只查需要字段;
    • 批量操作(INSERT INTO ... VALUES (),(),());
    • 避免 OR/IN,用 EXISTS 替代;
    • 分页优化:用主键游标分页(WHERE id > 1000 LIMIT 10);
  3. 执行计划:用 EXPLAIN 分析,优先优化 type=ALL(全表扫描)、Extra=Using filesort/Using temporary

  4. 数据库配置:调大 innodb_buffer_pool_size(内存 70%),开启查询缓存(读多写少场景)。

问题 11:联合索引怎么走的?

答案:联合索引(如 idx_a_b_c(a,b,c))按 “最左前缀原则” 匹配,索引树按 a→b→c 排序,查询规则:

  1. 匹配最左前缀:WHERE a=1/a=1 AND b=2/a=1 AND b=2 AND c=3 均走索引;
  2. 不匹配最左前缀:WHERE b=2/WHERE c=3 不走索引;WHERE a=1 AND c=3 仅 a 走索引,c 不走;
  3. 范围查询截断:WHERE a=1 AND b>2 AND c=3,a、b 走索引,c 因范围查询后失效,不走索引;
  4. 排序优化:ORDER BY a,b,c 可利用索引排序,避免 Using filesort

问题 12:Mysql 执行计划(EXPLAIN)怎么看?

答案

(1)核心字段说明

字段含义最优值 / 关键说明
id查询执行顺序数字越大越先执行,相同则从上到下
type访问类型const > eq_ref > ref > range > ALL
key实际使用的索引不为 NULL 表示走索引
rows预估扫描行数越少越好
Extra额外信息Using index(最优)、Using filesort/Using temporary(需优化)

(2)实战示例(带答案)

SQLSELECT amount FROM t_order WHERE user_id=10086 AND create_time > '2024-01-01';执行计划

idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEt_orderrangeidx_user_id,idx_ctidx_user_id100Using where; Using index condition

分析答案

  1. type=range:走范围索引,优于 ALL 但差于 ref;
  2. key=idx_user_id:仅使用 user_id 索引,create_time 未走索引;
  3. Extra=Using index condition:有索引下推,但未覆盖索引(需回表查 amount);
  4. 优化:创建 idx_user_ct_amount(user_id, create_time, amount) 覆盖索引,Extra 变为 Using index,无回表。