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
英文地址,請注明出處:http://en.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;確保其可…