Java后端高频面试题

Java后端高频面试题

目录

  1. Java集合框架
  2. Java并发编程
  3. JVM相关
  4. MySQL数据库
  5. Redis缓存
  6. Spring框架

Java集合框架

HashMap的数据结构是什么,为什么在JDK8要引入红黑树?

HashMap数据结构:

  • JDK7:数组 + 链表
  • JDK8:数组 + 链表 + 红黑树

引入红黑树的原因:

  1. 性能优化:当链表长度过长时,查询效率从O(1)退化为O(n)
  2. 阈值设计:当链表长度达到或超过8时,考虑转换为红黑树(注意是考虑,不是立即转换),只有当HashMap的数组容量达到或超过64时,才会真正执行树化操作。≤6时转回链表
  3. 平衡性能:红黑树保证最坏情况下O(log n)的查询时间复杂度

HashMap的扩容机制是什么,为什么其数组长度是2的幂次?

扩容机制:

  1. 触发条件:当size > threshold(容量 × 负载因子0.75)时触发扩容
  2. 扩容过程:数组长度扩大为原来的2倍,重新hash分布元素
  3. rehash优化:JDK8中元素要么在原位置,要么在原位置+oldCap

2的幂次的原因:
一种更高效的取模运算,只用当length为2的幂次时,才可以用位运算替代

// 计算索引位置:hash & (length - 1)
// 当length为2的幂次时,length-1的二进制全为1
// 例如:length=16时,length-1=15(二进制1111)
// 这样可以充分利用hash值的所有位,减少hash冲突

为什么在JDK7到8要把头插改为尾插?

头插法问题(JDK7):

  1. 死循环风险:多线程扩容时可能形成环形链表
  2. 线程不安全:并发操作可能导致数据丢失

尾插法优势(JDK8):

  1. 避免死循环:保持原有顺序,不会形成环
  2. 更直观:插入顺序更符合逻辑
  3. 配合红黑树:为树化做准备

为什么它解决问题的方式是用链表加红黑树?

设计考虑:

  1. 兼容性:保持原有链表结构的简单性
  2. 性能平衡:红黑树维护成本比AVL树低
  3. 动态调整:根据冲突程度动态选择数据结构
  4. 空间效率:红黑树节点比链表节点占用更多空间,只在必要时使用

ArrayList和LinkedList的区别是什么?

特性ArrayListLinkedList
底层结构动态数组双向链表
随机访问O(1)O(n)
插入删除(头尾)O(n)O(1)
插入删除(中间)O(n)O(1)
内存占用较少较多(存储指针)
缓存友好性

使用建议:

  • 频繁随机访问:ArrayList
  • 频繁插入删除:LinkedList
  • 内存敏感:ArrayList

Java并发编程

ConcurrentHashMap是怎么实现的,其在JDK7到8做了什么升级?

JDK7实现:

// 分段锁(Segment)+ HashEntry数组
// 默认16个Segment,每个Segment管理一部分数据
// 并发度 = Segment数量

JDK8升级:

// Node数组 + CAS + synchronized
// 取消Segment,使用Node数组
// 锁粒度更细:只锁链表头节点或红黑树根节点
// 并发度 = 数组长度

主要改进:

  1. 更高并发度:从16提升到数组长度
  2. 更少内存占用:去除Segment层级
  3. 更好性能:CAS + 局部锁

什么是乐观锁和悲观锁?

悲观锁:

  • 概念:假设会发生冲突,每次操作都加锁
  • 实现:synchronized、ReentrantLock
  • 适用场景:写多读少

乐观锁:

  • 概念:假设不会发生冲突,操作时检查是否被修改
  • 实现:CAS、版本号机制
  • 适用场景:读多写少

CAS是怎么实现的?

CAS(Compare And Swap):

// 伪代码
boolean compareAndSwap(int expectedValue, int newValue) {if (currentValue == expectedValue) {currentValue = newValue;return true;}return false;
}

底层实现:

  1. 硬件支持:CPU提供原子性指令
  2. 内存屏障:保证可见性和有序性
  3. 自旋机制:失败时重试

ABA问题解决:

  • 使用版本号(AtomicStampedReference)
  • 使用标记位(AtomicMarkableReference)

synchronized和ReentrantLock有什么区别?

特性synchronizedReentrantLock
实现方式JVM内置JDK实现
锁释放自动手动(finally)
公平性非公平可选公平/非公平
条件等待wait/notifyCondition
中断响应不可中断可中断
尝试获取锁不支持tryLock()

原子类是如何实现的?

核心机制:

public class AtomicInteger {private volatile int value;public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}
}

实现要点:

  1. volatile保证可见性
  2. Unsafe类提供CAS操作
  3. 自旋重试机制

volatile关键字有什么用?

主要作用:

  1. 保证可见性:修改立即刷新到主内存
  2. 禁止指令重排序:通过内存屏障实现
  3. 不保证原子性:复合操作仍需同步

使用场景:

  • 状态标记
  • 双重检查锁定
  • 单例模式

什么是JMM?

Java内存模型(JMM):

  • 定义:规范多线程中变量访问规则
  • 主内存:所有线程共享的内存区域
  • 工作内存:每个线程的私有内存区域

核心规则:

  1. 所有变量存储在主内存
  2. 线程对变量的操作在工作内存进行
  3. 工作内存与主内存同步

什么是指令重排序?

概念:
编译器和处理器为优化性能,可能改变指令执行顺序

类型:

  1. 编译器重排序:编译时优化
  2. 指令级重排序:CPU执行时优化
  3. 内存系统重排序:缓存和写缓冲区优化

影响:
在多线程环境下可能导致程序行为异常

什么是happens-before原则?

核心规则:

  1. 程序顺序规则:单线程内按程序顺序执行
  2. 监视器锁规则:unlock happens-before lock
  3. volatile规则:写 happens-before 读
  4. 线程启动规则:start() happens-before 线程内操作
  5. 线程终止规则:线程操作 happens-before join()
  6. 传递性:A happens-before B,B happens-before C,则A happens-before C

synchronized的锁升级流程

升级路径:

无锁 → 偏向锁 → 轻量级锁 → 重量级锁

详细流程:

  1. 偏向锁:单线程访问,在对象头记录线程ID
  2. 轻量级锁:多线程竞争不激烈,使用CAS
  3. 重量级锁:竞争激烈,使用操作系统互斥量

synchronized是不是可重入锁,可重入锁是为了保证什么?

可重入性:

  • synchronized是可重入锁
  • 同一线程可以多次获取同一把锁

实现机制:

// 锁记录中维护获取次数
// 每次重入计数+1,退出时计数-1
// 计数为0时释放锁

保证目的:

  1. 避免死锁:防止线程自己阻塞自己
  2. 支持递归调用
  3. 简化编程模型

AQS队列是怎么实现的,其如何实现一个公平锁?

AQS(AbstractQueuedSynchronizer):

// 核心结构:双向链表 + state状态
static final class Node {Node prev;Node next;Thread thread;int waitStatus;
}

实现机制:

  1. 状态管理:使用int state表示同步状态
  2. 队列管理:FIFO队列管理等待线程
  3. 模板方法:子类实现具体的同步语义

公平锁实现:

protected final boolean tryAcquire(int acquires) {// 检查队列中是否有等待的线程if (hasQueuedPredecessors()) {return false;}// 尝试CAS获取锁return compareAndSetState(0, acquires);
}

线程池的核心参数是什么,它提交任务的流程是怎么样的,核心参数如何计算?

核心参数:

ThreadPoolExecutor(int corePoolSize,      // 核心线程数int maximumPoolSize,   // 最大线程数long keepAliveTime,    // 空闲线程存活时间TimeUnit unit,         // 时间单位BlockingQueue<Runnable> workQueue, // 任务队列ThreadFactory threadFactory,       // 线程工厂RejectedExecutionHandler handler   // 拒绝策略
)

提交流程:

  1. 当前线程数 < corePoolSize:创建新线程
  2. 核心线程已满:任务入队
  3. 队列已满且线程数 < maximumPoolSize:创建新线程
  4. 达到最大线程数:执行拒绝策略

参数计算:

// CPU密集型:核心线程数 = CPU核数 + 1
// IO密集型:核心线程数 = CPU核数 × (1 + IO时间/CPU时间)

JVM相关

接口和抽象类的区别

特性接口抽象类
继承关系implements(可多个)extends(单个)
方法实现JDK8前只能抽象方法可以有具体实现
变量public static final任意访问修饰符
构造方法不能有可以有
设计理念能力契约模板设计

什么是单例模式?

定义:
确保一个类只有一个实例,并提供全局访问点

实现方式:

  1. 饿汉式:类加载时创建
  2. 懒汉式:首次使用时创建
  3. 双重检查锁定:线程安全的懒汉式
  4. 枚举实现:最安全的实现

写一个双重锁检查

public class Singleton {private volatile static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {  // 第一次检查synchronized (Singleton.class) {if (instance == null) {  // 第二次检查instance = new Singleton();}}}return instance;}
}

关键点:

  1. volatile关键字:防止指令重排序
  2. 双重检查:减少同步开销
  3. synchronized:保证线程安全

JVM调优做过吗?

调优目标:

  1. 降低GC频率:减少Stop-The-World时间
  2. 提高吞吐量:单位时间处理更多任务
  3. 降低延迟:减少响应时间

常用参数:

# 堆内存设置
-Xms2g -Xmx2g# 新生代设置
-Xmn800m# 垃圾收集器选择
-XX:+UseG1GC# GC日志
-XX:+PrintGC -XX:+PrintGCDetails

JVM垃圾回收算法

标记-清除(Mark-Sweep):

  • 标记所有需要回收的对象,然后清除
  • 优点:简单
  • 缺点:产生内存碎片

复制算法(Copying):

  • 将内存分为两块,每次只使用一块
  • 优点:无碎片,效率高
  • 缺点:内存利用率低

标记-整理(Mark-Compact):

  • 标记后将存活对象向一端移动
  • 优点:无碎片,内存利用率高
  • 缺点:移动对象成本高

分代收集

  • 新生代:复制算法
  • 老年代:标记-清除或标记-整理

JVM内存空间如何分配?

堆内存:

  • 新生代:Eden + Survivor0 + Survivor1(8:1:1)
  • 老年代:长期存活的对象

非堆内存:

  • 方法区:类信息、常量池
  • 直接内存:NIO使用
  • 栈内存:局部变量、方法调用

分配流程:

  1. 新对象在Eden区分配
  2. Eden满时触发Minor GC
  3. 存活对象进入Survivor区
  4. 经过多次GC后进入老年代

什么是逃逸分析?

定义:
分析对象的作用域,判断对象是否会逃出方法或线程

优化策略:

  1. 栈上分配:不逃逸的对象可在栈上分配
  2. 标量替换:将对象分解为基本类型
  3. 锁消除:消除不必要的同步

判断条件:

  • 对象是否被返回
  • 对象是否被外部引用
  • 对象是否在其他线程中使用

如何避免Out Of Memory这个错误?

预防措施:

  1. 合理设置堆内存:-Xms和-Xmx
  2. 避免内存泄漏:及时释放资源
  3. 使用对象池:重用对象
  4. 选择合适的数据结构

排查步骤:

  1. 堆dump分析:使用MAT、jhat
  2. 监控工具:jstat、jvisualvm
  3. 代码review:检查循环引用、大对象

你项目中遇到过内存泄漏的问题吗,如何解决?

常见内存泄漏场景:

  1. 集合类持有对象引用
  2. 监听器未正确移除
  3. 数据库连接未关闭
  4. ThreadLocal使用不当

解决方案:

// 使用try-with-resources
try (Connection conn = getConnection()) {// 数据库操作
}// ThreadLocal及时清理
threadLocal.remove();// 弱引用处理监听器
WeakHashMap<Object, Listener> listeners;

MySQL数据库

MySQL的事务隔离级别有哪些?

四个隔离级别:

  1. READ UNCOMMITTED(读未提交)

    • 最低级别,可能出现脏读、不可重复读、幻读
  2. READ COMMITTED(读已提交)

    • Oracle默认级别,避免脏读,可能出现不可重复读、幻读
  3. REPEATABLE READ(可重复读)

    • MySQL默认级别,避免脏读、不可重复读,可能出现幻读
  4. SERIALIZABLE(序列化)

    • 最高级别,避免所有问题,但性能最差

什么是ACID?

原子性(Atomicity):

  • 事务中的所有操作要么全部成功,要么全部失败
  • 通过undo log实现

一致性(Consistency):

  • 事务执行前后,数据库状态保持一致
  • 通过其他三个特性保证

隔离性(Isolation):

  • 并发事务之间相互隔离,不受影响
  • 通过锁和MVCC实现

持久性(Durability):

  • 事务提交后,对数据的修改永久保存
  • 通过redo log实现

在MVCC机制下可重复读是怎么实现的,它还会幻读吗?

MVCC实现原理:

-- 每行记录包含两个隐藏字段
-- trx_id: 创建该版本的事务ID
-- roll_pointer: 指向undo log的指针-- ReadView包含:
-- m_ids: 当前活跃事务列表
-- min_trx_id: 最小活跃事务ID
-- max_trx_id: 下一个事务ID
-- creator_trx_id: 创建ReadView的事务ID

可重复读实现:

  1. 事务开始时创建ReadView
  2. 根据ReadView判断数据版本可见性
  3. 整个事务期间使用同一个ReadView

幻读问题:

  • 快照读:通过MVCC避免幻读
  • 当前读:通过Next-Key Lock避免幻读

什么是间隙锁,什么是临键锁?

间隙锁(Gap Lock):

  • 锁定记录之间的间隙,防止插入新记录
  • 只在可重复读级别下生效

临键锁(Next-Key Lock):

  • 记录锁 + 间隙锁的组合
  • 锁定记录本身和记录前面的间隙

示例:

-- 假设索引值:1, 3, 5, 7, 10
-- 查询条件:WHERE id > 3 AND id < 7
-- Gap Lock: (3,5), (5,7)
-- Next-Key Lock: (3,5], (5,7]

什么是索引的回表查询,如何避免?

回表查询:
通过二级索引找到主键值,再通过主键索引查找完整记录

避免方法:

  1. 覆盖索引:查询字段都在索引中
  2. 联合索引:将常用查询字段组合成索引
  3. 主键选择:使用自增主键减少回表

示例:

-- 需要回表
SELECT * FROM user WHERE name = 'John';-- 覆盖索引,无需回表
SELECT id, name FROM user WHERE name = 'John';

MySQL有哪些常见的索引?

按数据结构分类:

  1. B+Tree索引:InnoDB默认索引类型
  2. Hash索引:Memory存储引擎支持
  3. Full-Text索引:全文检索

按物理存储分类:

  1. 聚簇索引:数据和索引存储在一起(主键索引)
  2. 非聚簇索引:索引和数据分别存储(二级索引)

按逻辑分类:

  1. 主键索引:唯一且不为空
  2. 唯一索引:值唯一但可为空
  3. 普通索引:无唯一性限制
  4. 联合索引:多个字段组合

索引在什么情况下会失效?

常见失效场景:

-- 1. 违反最左前缀原则
-- 索引:(a, b, c)
SELECT * FROM t WHERE b = 1 AND c = 1;  -- 失效-- 2. 使用函数或计算
SELECT * FROM t WHERE UPPER(name) = 'JOHN';  -- 失效-- 3. 类型转换
SELECT * FROM t WHERE id = '123';  -- 可能失效-- 4. 使用NOT、!=、<>
SELECT * FROM t WHERE name != 'John';  -- 失效-- 5. LIKE以通配符开头
SELECT * FROM t WHERE name LIKE '%John';  -- 失效-- 6. OR条件中有未建索引的字段
SELECT * FROM t WHERE name = 'John' OR age = 25;  -- 可能失效

explain关键字

主要字段:

EXPLAIN SELECT * FROM user WHERE name = 'John';-- id: 查询序列号
-- select_type: 查询类型(SIMPLE、PRIMARY、SUBQUERY等)
-- table: 表名
-- type: 访问类型(system > const > eq_ref > ref > range > index > ALL)
-- possible_keys: 可能使用的索引
-- key: 实际使用的索引
-- key_len: 索引长度
-- ref: 索引比较的列
-- rows: 扫描的行数
-- Extra: 额外信息

type字段重要性(性能从好到坏):

  • system/const: 最优
  • eq_ref: 唯一性索引扫描
  • ref: 非唯一性索引扫描
  • range: 范围扫描
  • index: 索引全扫描
  • ALL: 全表扫描(最差)

InnoDB下MySQL索引的数据结构是什么,为什么选它不选别的?

数据结构:B+Tree

选择原因:

  1. 减少磁盘IO:树高度低,通常3-4层
  2. 范围查询友好:叶子节点链表结构
  3. 插入性能好:相比红黑树更适合磁盘存储
  4. 空间利用率高:非叶子节点只存储键值

对比其他结构:

  • Hash:等值查询快,但不支持范围查询
  • 二叉树:树高度高,IO次数多
  • B-Tree:非叶子节点存储数据,空间利用率低

了解过MySQL的三大日志吗?

redo log(重做日志):

  • 作用:保证事务持久性
  • 机制:WAL(Write-Ahead Logging)
  • 刷盘策略:innodb_flush_log_at_trx_commit

undo log(回滚日志):

  • 作用:保证事务原子性,实现MVCC
  • 内容:记录数据修改前的值
  • 清理:事务提交后可以清理

binlog(二进制日志):

  • 作用:主从复制、数据恢复
  • 格式:STATEMENT、ROW、MIXED
  • 位置:MySQL Server层

Redis缓存

Redis有哪些数据类型,他们有哪些主要的应用场景?

基本数据类型:

  1. String(字符串)

    SET key value
    GET key
    
    • 应用:缓存、计数器、session存储
  2. Hash(哈希)

    HSET user:1 name "John" age 25
    HGET user:1 name
    
    • 应用:存储对象、用户信息
  3. List(列表)

    LPUSH list1 "a" "b" "c"
    RPOP list1
    
    • 应用:消息队列、最新列表
  4. Set(集合)

    SADD tags "java" "redis" "mysql"
    SINTER set1 set2
    
    • 应用:标签、去重、交集运算
  5. Sorted Set(有序集合)

    ZADD leaderboard 100 "player1" 200 "player2"
    ZRANGE leaderboard 0 -1
    
    • 应用:排行榜、优先级队列

缓存穿透、缓存击穿、缓存雪崩

缓存穿透:

  • 问题:查询不存在的数据,缓存和数据库都没有
  • 解决方案
    • 布隆过滤器
    • 缓存空值(设置较短过期时间)

缓存击穿:

  • 问题:热点key过期,大量请求直接打到数据库
  • 解决方案
    • 互斥锁重建缓存
    • 热点数据永不过期
    • 提前异步刷新

缓存雪崩:

  • 问题:大量key同时过期或Redis宕机
  • 解决方案
    • 过期时间随机化
    • 多级缓存
    • 限流降级

Redis和数据库的一致性问题怎么解决?

一致性策略:

  1. Cache Aside(旁路缓存)

    // 读取
    data = cache.get(key);
    if (data == null) {data = db.get(key);cache.set(key, data);
    }// 更新
    db.update(key, data);
    cache.delete(key);  // 删除缓存
    
  2. 延迟双删

    cache.delete(key);
    db.update(key, data);
    Thread.sleep(500);  // 延迟
    cache.delete(key);
    
  3. 使用消息队列

    • 数据库更新后发送消息
    • 消费者异步更新缓存

Redis为什么快,可以说说Redis的IO多路复用模型吗?

Redis快的原因:

  1. 内存存储:数据存储在内存中
  2. 单线程模型:避免线程切换和锁竞争
  3. 高效数据结构:针对性优化的数据结构
  4. IO多路复用:高效处理网络IO

IO多路复用模型:

Client1  ----\
Client2  ------> Redis Server (单线程)
Client3  ----/

工作原理:

  1. 事件循环:主线程在事件循环中处理IO事件
  2. 多路复用器:使用epoll/kqueue监听多个socket
  3. 非阻塞IO:socket设置为非阻塞模式
  4. 事件驱动:有数据可读/写时触发事件

用Redis实现一套登录的机制

实现方案:

// 登录
public String login(String username, String password) {// 验证用户名密码if (validateUser(username, password)) {// 生成tokenString token = UUID.randomUUID().toString();// 存储到Redis,设置过期时间String key = "session:" + token;Map<String, String> userInfo = new HashMap<>();userInfo.put("username", username);userInfo.put("loginTime", String.valueOf(System.currentTimeMillis()));redisTemplate.opsForHash().putAll(key, userInfo);redisTemplate.expire(key, 30, TimeUnit.MINUTES);return token;}return null;
}// 验证登录状态
public boolean isLogin(String token) {String key = "session:" + token;return redisTemplate.hasKey(key);
}// 登出
public void logout(String token) {String key = "session:" + token;redisTemplate.delete(key);
}// 续期
public void refreshSession(String token) {String key = "session:" + token;if (redisTemplate.hasKey(key)) {redisTemplate.expire(key, 30, TimeUnit.MINUTES);}
}

用Redis做防抖和节流

防抖实现(Debounce):

public class RedisDebounce {@Autowiredprivate RedisTemplate<String, String> redisTemplate;public boolean debounce(String key, long delayMs) {String debounceKey = "debounce:" + key;// 设置键值,如果键已存在则不执行Boolean result = redisTemplate.opsForValue().setIfAbsent(debounceKey, "1", delayMs, TimeUnit.MILLISECONDS);return result != null && result;}
}

节流实现(Throttle):

public class RedisThrottle {@Autowiredprivate RedisTemplate<String, String> redisTemplate;// 固定窗口节流public boolean throttle(String key, int maxRequests, long windowMs) {String throttleKey = "throttle:" + key;Long count = redisTemplate.opsForValue().increment(throttleKey);if (count == 1) {redisTemplate.expire(throttleKey, windowMs, TimeUnit.MILLISECONDS);}return count <= maxRequests;}// 滑动窗口节流public boolean slidingWindowThrottle(String key, int maxRequests, long windowMs) {String throttleKey = "sliding:" + key;long now = System.currentTimeMillis();long windowStart = now - windowMs;// 使用Lua脚本保证原子性String luaScript = "redis.call('zremrangebyscore', KEYS[1], 0, ARGV[1]) " +"local count = redis.call('zcard', KEYS[1]) " +"if count < tonumber(ARGV[2]) then " +"    redis.call('zadd', KEYS[1], ARGV[3], ARGV[3]) " +"    redis.call('expire', KEYS[1], ARGV[4]) " +"    return 1 " +"else " +"    return 0 " +"end";Long result = (Long) redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),Collections.singletonList(throttleKey),String.valueOf(windowStart),String.valueOf(maxRequests),String.valueOf(now),String.valueOf(windowMs / 1000));return result != null && result == 1;}
}

Redis主从同步

主从复制原理:

  1. 全量同步

    • 从库发送PSYNC命令
    • 主库执行BGSAVE生成RDB文件
    • 主库将RDB文件发送给从库
    • 从库加载RDB文件
  2. 增量同步

    • 主库将写命令记录到复制缓冲区
    • 异步发送给从库执行

配置示例:

# 从库配置
slaveof 192.168.1.100 6379
# 或
replicaof 192.168.1.100 6379# 只读模式
slave-read-only yes

主从切换(哨兵模式):

# 哨兵配置
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000

Redis的持久化机制

RDB(Redis Database):

  • 原理:某个时间点的数据快照
  • 触发条件
    save 900 1      # 900秒内至少1个key变化
    save 300 10     # 300秒内至少10个key变化
    save 60 10000   # 60秒内至少10000个key变化
    
  • 优点:文件小,恢复快
  • 缺点:可能丢失数据

AOF(Append Only File):

  • 原理:记录所有写操作命令
  • 同步策略
    appendfsync always    # 每次写入都同步
    appendfsync everysec  # 每秒同步(默认)
    appendfsync no        # 操作系统决定何时同步
    
  • 优点:数据安全性高
  • 缺点:文件大,恢复慢

混合持久化(RDB + AOF):

aof-use-rdb-preamble yes
  • RDB作为基础数据
  • AOF记录RDB之后的增量操作

Redis中的大key和热key如何去优化?

大key优化:

  1. 拆分策略

    // 原来:一个大hash
    HSET user:info name age email address ...// 拆分后:多个小hash
    HSET user:basic name age
    HSET user:contact email phone
    HSET user:address province city
    
  2. 分片存储

    // 将大list分片存储
    for (int i = 0; i < totalSize; i += batchSize) {String shardKey = key + ":" + (i / batchSize);// 存储分片数据
    }
    

热key优化:

  1. 多级缓存

    // 本地缓存 + Redis缓存
    Object data = localCache.get(key);
    if (data == null) {data = redisCache.get(key);if (data != null) {localCache.put(key, data);}
    }
    
  2. 读写分离

    • 读操作分散到多个从节点
    • 热key复制到多个实例
  3. 热key发现

    // 使用监控工具
    redis-cli --hotkeys
    // 或使用应用层统计
    

Spring框架

Spring Boot设计了哪些设计模式?

主要设计模式:

  1. 单例模式:Spring Bean默认是单例
  2. 工厂模式:BeanFactory创建Bean实例
  3. 代理模式:AOP的实现基础
  4. 模板方法模式:JdbcTemplate、RestTemplate
  5. 观察者模式:ApplicationEvent事件机制
  6. 策略模式:不同的Bean创建策略
  7. 装饰器模式:BeanWrapper装饰Bean
  8. 适配器模式:HandlerAdapter适配不同Controller

什么是IOC?

控制反转(Inversion of Control):

  • 定义:将对象的创建和依赖关系的管理交给容器
  • 核心思想:不要主动创建依赖对象,而是被动接收

DI(依赖注入)实现方式:

// 1. 构造器注入
@Component
public class UserService {private final UserRepository userRepository;public UserService(UserRepository userRepository) {this.userRepository = userRepository;}
}// 2. Setter注入  
@Component
public class UserService {private UserRepository userRepository;@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}
}// 3. 字段注入
@Component
public class UserService {@Autowiredprivate UserRepository userRepository;
}

SpringBoot的循环依赖怎么解决?

循环依赖场景:

@Component
public class A {@Autowiredprivate B b;
}@Component  
public class B {@Autowiredprivate A a;
}

解决机制(三级缓存):

// 第一级缓存:成品对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();// 第二级缓存:半成品对象  
private final Map<String, Object> earlySingletonObjects = new HashMap<>();// 第三级缓存:对象工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();

解决流程:

  1. 创建A的实例,放入三级缓存
  2. 填充A的属性,发现需要B
  3. 创建B的实例,放入三级缓存
  4. 填充B的属性,发现需要A
  5. 从三级缓存获取A的实例,放入二级缓存
  6. B创建完成,放入一级缓存
  7. A创建完成,放入一级缓存

AOP是怎么实现的?

AOP(面向切面编程)实现原理:

  1. JDK动态代理(接口代理):

    public class JdkProxyExample implements InvocationHandler {private Object target;public Object invoke(Object proxy, Method method, Object[] args) {// 前置通知System.out.println("Before method: " + method.getName());Object result = method.invoke(target, args);// 后置通知System.out.println("After method: " + method.getName());return result;}
    }
    
  2. CGLIB代理(类代理):

    public class CglibProxyExample implements MethodInterceptor {public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("Before method: " + method.getName());Object result = proxy.invokeSuper(obj, args);System.out.println("After method: " + method.getName());return result;}
    }
    

AOP配置示例:

@Aspect
@Component
public class LoggingAspect {@Pointcut("execution(* com.example.service.*.*(..))")public void serviceMethods() {}@Before("serviceMethods()")public void logBefore(JoinPoint joinPoint) {System.out.println("执行方法: " + joinPoint.getSignature().getName());}@AfterReturning(pointcut = "serviceMethods()", returning = "result")public void logAfterReturning(JoinPoint joinPoint, Object result) {System.out.println("方法返回: " + result);}
}

除了JDK的动态代理你还了解过其他的代理模式吗?

静态代理:

// 接口
interface UserService {void addUser(String name);
}// 目标类
class UserServiceImpl implements UserService {public void addUser(String name) {System.out.println("添加用户: " + name);}
}// 代理类
class UserServiceProxy implements UserService {private UserService userService;public UserServiceProxy(UserService userService) {this.userService = userService;}public void addUser(String name) {System.out.println("权限检查");userService.addUser(name);System.out.println("日志记录");}
}

字节码生成代理:

  • CGLIB:运行时生成字节码
  • Javassist:编译时字节码操作
  • ASM:低级别字节码操作框架

Spring的AOP是运行时代理还是编译时代理?

Spring AOP是运行时代理:

  • 在应用启动时创建代理对象
  • 使用JDK动态代理或CGLIB生成代理类
  • 代理对象在运行时拦截方法调用

对比编译时代理:

  • AspectJ:编译时织入,性能更好
  • Spring AOP:运行时代理,使用简单

配置AspectJ编译时织入:

<plugin><groupId>org.aspectj</groupId><artifactId>aspectj-maven-plugin</artifactId><configuration><complianceLevel>1.8</complianceLevel></configuration>
</plugin>

事务注解什么时候失效?

常见失效场景:

  1. 方法不是public

    @Transactional
    private void updateUser() {  // 失效// 更新操作
    }
    
  2. 自调用问题

    @Service
    public class UserService {public void method1() {this.method2();  // 失效,没有通过代理调用}@Transactionalpublic void method2() {// 事务方法}
    }
    
  3. 异常被捕获

    @Transactional
    public void updateUser() {try {// 数据库操作} catch (Exception e) {// 异常被捕获,事务不回滚}
    }
    
  4. 异常类型不匹配

    @Transactional  // 默认只回滚RuntimeException
    public void updateUser() throws Exception {throw new Exception();  // 不会回滚
    }// 正确配置
    @Transactional(rollbackFor = Exception.class)
    public void updateUser() throws Exception {throw new Exception();  // 会回滚
    }
    
  5. 数据库引擎不支持

    • MyISAM不支持事务
    • 需要使用InnoDB引擎

解决方案:

// 1. 使用ApplicationContext获取代理对象
@Autowired
private ApplicationContext applicationContext;public void method1() {UserService proxy = applicationContext.getBean(UserService.class);proxy.method2();
}// 2. 使用@EnableAspectJAutoProxy(exposeProxy = true)
public void method1() {UserService proxy = (UserService) AopContext.currentProxy();proxy.method2();
}// 3. 注入自己
@Autowired
private UserService userService;public void method1() {userService.method2();
}

总结

本文涵盖了Java后端开发中的核心知识点,包括:

  • 集合框架:HashMap、ArrayList等核心数据结构的实现原理
  • 并发编程:线程安全、锁机制、线程池等并发处理技术
  • JVM调优:内存管理、垃圾回收、性能优化策略
  • 数据库:MySQL事务、索引、查询优化等数据库技术
  • 缓存技术:Redis数据类型、持久化、集群等缓存方案
  • 框架原理:Spring IOC、AOP、事务管理等框架核心

这些知识点不仅是面试的重点,更是日常开发中需要深入理解和灵活运用的核心技术。建议结合实际项目经验,深入理解每个技术点的适用场景和最佳实践。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/917737.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/917737.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

37. line-height: 1.2 与 line-height: 120% 的区别

概述 line-height 是 CSS 中用于控制文本行间距的重要属性。虽然 line-height: 1.2 和 line-height: 120% 看似相同&#xff0c;但它们在计算方式上存在关键区别&#xff0c;尤其是在继承和计算值方面。1. 计算方式不同写法类型计算方式说明line-height: 1.2无单位&#xff08;…

蓝桥杯----DS1302实时时钟

&#xff08;六&#xff09;、DS1302实时时钟1、原理&#xff08;图 二十六&#xff09;DS1302通过三线串行接口与单片机进行通信。微控制器可以通过设置RST引脚为高电平来使能DS1302&#xff0c;并通过SCK引脚提供串行时钟信号&#xff0c;然后通过I/O引脚进行数据的读写操作。…

C++对象访问有访问权限是不是在ide里有效

在C中&#xff0c;对象的访问权限&#xff08;即公有&#xff08;public&#xff09;、保护&#xff08;protected&#xff09;和私有&#xff08;private&#xff09;成员的访问&#xff09;是编译时的一部分&#xff0c;而不是运行时。这意味着&#xff0c;无论是在IDE&#…

CubeMX安装芯片包

1.点击HELP2.选择公理嵌入式软件包3.选择并下载芯片包

【面向对象】面向对象七大原则

设计模式 设计模式是什么&#xff1f; 设计模式是一种针对于反复提出问题的解决方案&#xff0c;是经过长时间经验和试错而总结出来的一套业务流程&#xff1b; 其目的是为了提高代码的可重用性和可维护性&#xff0c;让代码更容易让人理解&#xff0c;保证代码可靠性&#…

《计算机“十万个为什么”》之 面向对象 vs 面向过程:编程世界的积木与流水线

《计算机“十万个为什么”》之 面向对象 vs 面向过程&#xff1a;编程世界的积木与流水线 &#x1f916; 想象你要造一辆汽车&#x1f527;&#xff1a; 面向过程 按手册一步步拧螺丝&#xff1a;拧紧螺栓A → 安装轮胎B → 焊接车架C 面向对象 召唤汽车人战队&#xff1a;引…

Visual Studio Code (VSCode) 的常用快捷键

Visual Studio Code (VSCode) 的常用快捷键可极大提升开发效率。以下是分类整理的 **核心快捷键**&#xff08;基于 **Windows/Linux** 系统&#xff0c;macOS 用户将 Ctrl 替换为 Cmd&#xff0c;Alt 替换为 Option&#xff09;&#xff1a;⚡ 基础操作快捷键功能Ctrl N新建文…

vite面试题及详细答案120题(01-30)

《前后端面试题》专栏集合了前后端各个知识模块的面试题&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

Cesium学习(一)-基础

Cesium是一个开源的JavaScript库&#xff0c;专门用于创建3D地球和地图可视化。它在GIS、航空航天、城市规划等领域有广泛应用。 Cesium核心特性3D地球可视化 基于WebGL的高性能3D渲染支持全球地形和影像数据准确的地球模型&#xff08;WGS84椭球体&#xff09;多维数据支持 时…

饿了么招java开发咯

研发工程师-JAVA/Golang&#xff08;岗位信息已经过jobleap.cn授权&#xff0c;可以在CSDN发布&#xff09;饿了么 杭州收录时间&#xff1a; 2025年08月05日职位描述1、参与基础软件的设计、开发和维护&#xff0c;如分布式中间件、DevOps平台、应用监控系统等&#xff1b; 2…

java web 未完成项目,本来想做个超市管理系统,前端技术还没学。前端是个简单的html。后端接口比较完善。

代码结构 超市管理系统/├── src/ │ ├── com/ │ │ └── zhang/ │ ├── documents.txt │ ├── documents_detail.txt │ ├── goods.txt │ ├── order.txt │ ├── order_detail.txt │ ├── role.txt │ ├── tb_test.txt │ …

R语言基础图像及部分调用函数

R语言基础图像及部分调用函数 散点图 散点图是将所有的数据以点的形式展现在直角坐标系上&#xff0c;以显示变量之间的相互影响程度&#xff0c;点的位置由变量的数值决定&#xff0c;每个点对应一个 X 和 Y 轴点坐标。 散点图可以使用 plot() 函数来绘制 例子 x<-c(10,40)…

自由学习记录(77)

官方模版、、都不用了&#xff0c;记得之前用gitextension 的时候也好像有这种问题&#xff0c;也不知道怎么回事 用自己的就行了 网上说什么都没用&#xff0c;还是要自己老实写&#xff0c;配上截图工具截屏目录直接转文字过去&#xff0c;其实字都不要打多少的 一张很深刻…

运动想象 (MI) 分类学习系列 (18) : MSVTNet

运动想象分类学习系列:用于基于脑电图的运动图像解码的多尺度视觉转换器神经网络 0. 引言 1. 主要贡献 2. 方法![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/65a03fcd4a9144f6a7324b0969fd9d4e.png#pic_center) 3. 结果 3.1 脑电图数据预处理 3.2 解码性能比较 3.3…

Spring 03 Web springMVC

Springboot 常用 Spring MVC 实现 web 服务。 Spring MVC 请求处理流程图片来自《Spring 实战第四版》 浏览器请求首先被交给 DispatcherServlet 前端控制器。 DispatcherServlet 查询处理器映射以决定将请求发送给哪个控制器。控制器处理业务逻辑后&#xff0c;向 DispatcherS…

大厂面试题

线程池的状态?回答:running->shutdown->stop->tidyng->TERMINATED 线程池状态怎么流转2. 回答:变成shutdown&#xff0c;执行shutdown()函数变成stop&#xff0c;执行shutdownnow函数 变成tining&#xff0c;所有任务已处理完 变成TERMINATED&#xff0c;线程池调…

达芬奇31-40

快捷键C鼠标左键拖拽到节点上 A鼠标左键拖拽节点 复制到另一个图层上Raw素材太哦调整为log方便调色磨皮中间调向左磨皮,向右变老找到丢失的高光磨皮后脸部高光消失,或不明显,此时用亮度吸管工具找到脸部的高光,拉高中灰和亮部的Y值质感纹理增强器Tiny,Fine高频细节(脸部)增强或…

dify

一、SVG Logo Design ### 任务 我希望你充当图像生成的提示生成器。 ### 任务描述 你的工作是提供详细且富有创意的描述&#xff0c;以激发 AI 生成独特而有趣的图像。请记住&#xff0c;格式应遵循以下一般模式&#xff1a; <主要主题>, <主要主题的描述>, <背…

Mysql 实战问题处理速通

文章目录创建账号和授权查询没有主键的表统计每个库大小前十张大表清理日志表Prepared statement needs to be re-preparedxtrabackup 问题锁问题处理快速处理查询事务等待和阻塞情况innodb_trxprocesslistdata_locksdata_lock_waitsmetadata_locksevents_statements_current其…

如何测量滚珠花键的旋转方向间隙?

测量滚珠花键的旋转方向间隙需要使用适当的工具&#xff0c;通常情况下&#xff0c;可以使用游标卡尺或外径卡尺进行测量。这些工具可以准确地测量间隙的宽度和深度&#xff0c;并且可以轻松地记录测量结果。手动检测法&#xff1a;将滚珠花键固定在支架上&#xff0c;确保其可…