多線程(2)

多線程(2)

🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴

ThreadLocal什么時候會出現OOM的情況?為什么?

ThreadLocal 導致 OOM 的完整解析

ThreadLocal 是 Java 中用于實現線程本地存儲的核心工具,但其設計中隱含的內存管理陷阱可能導致 內存溢出(OOM)。本文將結合 ThreadThreadLocalThreadLocalMap 的源碼,深入分析 OOM 的觸發條件、底層邏輯,并給出解決方案。


一、ThreadLocal 的核心架構:Thread、ThreadLocal、ThreadLocalMap 的關系

ThreadLocal 的核心設計目標是 為每個線程維護獨立的變量副本,其底層依賴三個關鍵組件:

組件角色描述
Thread每個線程實例(Thread 對象)內部維護兩個 ThreadLocalMap 字段: - threadLocals:存儲當前線程的普通 ThreadLocal 變量 - inheritableThreadLocals:存儲可繼承的 ThreadLocal 變量(默認不啟用)
ThreadLocal<T>用戶使用的 API 類(如 threadLocal.set(value)),本質是 ThreadLocalMap 的 Key
ThreadLocalMap真正存儲數據的容器(類似 HashMap),每個 Thread 實例獨立擁有一個 ThreadLocalMap
1. Thread 類的源碼:存儲 ThreadLocalMap

Thread 類的源碼(JDK 8)中,threadLocalsinheritableThreadLocals 是存儲線程局部變量的核心字段:

public class Thread implements Runnable {// 存儲普通 ThreadLocal 變量的哈希表(用戶常用)ThreadLocal.ThreadLocalMap threadLocals = null;// 存儲可繼承 ThreadLocal 變量的哈希表(通過 InheritableThreadLocal 訪問)ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;// 線程終止時清理資源(關鍵方法)private void exit() {if (group != null) {group.threadTerminated(this);group = null;}// ... 其他資源清理(如棧、上下文等) ...threadLocals = null;       // 清空普通 ThreadLocal 變量inheritableThreadLocals = null; // 清空可繼承 ThreadLocal 變量}
}
  • threadLocals:用戶通過 ThreadLocal.set() 存儲的變量會存入此哈希表。
  • exit() 方法:線程終止時調用,清空 threadLocalsinheritableThreadLocals,釋放內存。
2. ThreadLocalMap 的源碼:存儲線程局部變量的容器

ThreadLocalMapThreadLocal 的靜態內部類,本質是一個自定義的哈希表,源碼核心結構如下:

static class ThreadLocalMap {// Entry 數組,存儲鍵值對(初始容量 16)private Entry[] table;// 擴容閾值(容量 * 負載因子,默認負載因子 0.75)private int threshold;// 構造函數(初始化數組和閾值)ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY]; // INITIAL_CAPACITY = 16int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY); // 閾值 = 16 * 0.75 = 12}// Entry 定義(繼承弱引用)static class Entry extends WeakReference<ThreadLocal<?>> {Object value; // 線程的局部變量副本(強引用)Entry(ThreadLocal<?> k, Object v) {super(k); // Key 是弱引用(指向 ThreadLocal 實例)value = v; // Value 是強引用(指向線程的局部變量)}}
}
  • Entry:繼承自 WeakReference<ThreadLocal<?>>,其 Key 是弱引用(指向 ThreadLocal 實例),Value 是強引用(指向線程的局部變量)。
  • 弱引用 Key 的意義:當 ThreadLocal 實例不再被外部引用時(如開發者主動移除或線程結束),Entry 的 Key 會被 GC 標記為可回收,避免內存泄漏。

二、ThreadLocal 的內存回收機制:為什么可能泄漏?

ThreadLocal 的內存回收依賴兩個層面:Key 的回收(ThreadLocal 實例)Value 的回收(線程的局部變量)。理解這兩個過程是定位 OOM 的關鍵。

1. Key 的回收:弱引用與 GC

Entry 的 Key 是弱引用(WeakReference<ThreadLocal<?>>),因此:

  • ThreadLocal 實例(如用戶定義的 threadLocal1不再被任何強引用指向時(例如開發者代碼中不再持有該變量),GC 會回收 Key(將其標記為 null)。
  • 此時,Entry 變為 無效條目(Key 為 null,但 Value 仍被強引用)。
2. Value 的回收:惰性清理機制

無效條目中的 Value 無法直接被 GC 回收(因為被 Entry 強引用),必須通過 ThreadLocalMap 的清理機制主動清除。清理觸發時機包括:

  • 調用 ThreadLocal.get():若發現當前 Key 對應的 Entry 已失效(Key 為 null),會觸發清理。
  • 調用 ThreadLocal.set():插入新 Entry 前,會清理當前哈希位置附近的無效條目。
  • 調用 ThreadLocal.remove():直接刪除當前 Key 對應的 Entry(最徹底的清理方式)。
  • 線程終止時Thread.exit() 方法會清空 threadLocals,釋放所有 Entry。
清理的局限性:惰性且不徹底

ThreadLocalMap 的清理是 惰性清理(Lazy Cleanup),僅在特定操作時觸發,且每次清理可能只處理部分無效條目(而非全部)。例如:

  • set() 方法中,插入新 Entry 前僅清理當前哈希位置附近的無效條目(expungeStaleEntry)。
  • get() 方法中,若發現 Key 為 null,僅清理當前 Entry,不會遍歷整個數組。

問題根源:如果開發者未主動調用 remove(),且線程長期存活(如線程池中的線程),無效條目會持續累積,導致 Value 無法釋放,最終引發 OOM。


三、OOM 的核心場景:線程池的長期存活線程

線程池(如 FixedThreadPool)的核心線程是 復用且長期存活 的(除非線程池被顯式銷毀)。結合 ThreadLocal 的清理機制,線程池會放大內存泄漏問題。

1. 線程池的線程生命周期

線程池(如 Executors.newFixedThreadPool(1))創建的線程會重復執行多個任務(Runnable),線程生命周期遠長于單個任務。例如:

ExecutorService pool = Executors.newFixedThreadPool(1); // 線程池只有1個核心線程
for (int i = 0; i < 100; i++) {pool.execute(() -> { // 任務邏輯:存儲大對象到 ThreadLocal});
}

該線程會執行 100 次任務,但線程本身不會被銷毀(除非線程池關閉)。

2. 任務中存儲大對象且未清理

假設每個任務向 ThreadLocal 中存儲一個 10MB 的大對象(如 byte[10 * 1024 * 1024]),但未調用 remove()

pool.execute(() -> {try {byte[] bigData = new byte[10 * 1024 * 1024]; // 10MB 大對象threadLocal.set(bigData); // 存儲到當前線程的 ThreadLocalMap 中// 任務結束,但未調用 threadLocal.remove()} catch (Exception e) {e.printStackTrace();}
});

此時:

  • 線程存活(線程池復用),Thread 對象的 threadLocals 不會被清空。
  • ThreadLocalMap 中的 Entry 因未調用 remove(),Key 雖被回收(變為 null),但 Value(10MB 數組)仍被強引用,無法回收。
3. 無效 Entry 持續累積導致 OOM

每次任務執行后,ThreadLocalMap 中會新增一個無效 Entry(Key 為 null,Value 為 10MB 數組)。由于線程存活,這些無效 Entry 不會被自動清理,最終導致:

  • ThreadLocalMaptable 數組被大量無效 Entry 占據(例如 100 次任務后,數組中有 100 個無效 Entry)。
  • 內存占用持續增長(100 次任務后約 1GB),最終觸發 OOM(OutOfMemoryError)。

四、源碼級分析:OOM 觸發的具體過程

通過 ThreadLocalMap 的核心方法源碼,詳細分析無效 Entry 如何累積并導致 OOM。

1. set() 方法:插入新 Entry 并觸發清理

ThreadLocal.set(T value) 方法的源碼(JDK 8)如下:

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = t.threadLocals; // 獲取當前線程的 ThreadLocalMapif (map != null) {// 計算 Key 的哈希位置int i = key.threadLocalHashCode & (map.table.length - 1);// 遍歷哈希位置,查找是否已存在當前 Keyfor (Entry e = map.table[i]; e != null; e = map.table[nextIndex(i, map.table.length)]) {ThreadLocal<?> k = e.get();if (k == key) { // Key 已存在:更新 Valuee.value = value;return;}if (k == null) { // 找到無效 Entry:替換并清理replaceStaleEntry(key, value, i);return;}}// 未找到現有 Key:插入新 Entrymap.table[i] = new Entry(key, value);int sz = ++map.size;// 檢查是否需要擴容或清理(閾值是容量的 0.75 倍)if (!map.cleanSomeSlots(i, sz) && sz >= map.threshold) {map.rehash(); // 擴容并重新哈希}} else {// 首次設置:初始化 ThreadLocalMapcreateMap(t, value);}
}
  • 關鍵邏輯:插入新 Entry 前,若發現無效 Entry(Key 為 null),會調用 replaceStaleEntry 替換該 Entry,但僅清理當前位置附近的無效條目,無法保證完全清理。
  • 擴容機制:當 size >= threshold(容量 * 0.75)時,觸發 rehash() 擴容(容量翻倍),但擴容前僅清理部分無效條目(cleanSomeSlots),無法徹底解決內存泄漏。
2. get() 方法:獲取值并觸發清理

ThreadLocal.get() 方法的源碼(JDK 8)如下:

public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = t.threadLocals;if (map != null) {// 計算 Key 的哈希位置int i = key.threadLocalHashCode & (map.table.length - 1);// 查找 EntryEntry e = map.table[i];if (e != null && e.get() == key) { // Key 存在且未失效return (T)e.value;}// Key 不存在或失效:觸發清理并遞歸查找return (T)expungeStaleEntry(map, i, null);}// 首次獲取:初始化 ThreadLocalMap(返回默認值)return setInitialValue();
}
  • expungeStaleEntry 方法:清理指定位置的無效 Entry,并將后續的無效 Entry 也一并清理(通過 expungeStaleEntries 遍歷數組)。
  • 局限性get() 僅清理當前哈希位置附近的無效條目,若無效條目分散在數組中,無法全部清理。
3. remove() 方法:主動清理 Entry

ThreadLocal.remove() 方法的源碼(JDK 8)如下:

public void remove() {ThreadLocalMap m = threadLocals;if (m != null && m.remove(this) != null) { // 調用 ThreadLocalMap 的 remove 方法m.remove(this); // 從 table 中刪除當前 Key 對應的 Entry}
}
  • ThreadLocalMap.remove(ThreadLocal<?> key):遍歷 table 數組,找到 Key 對應的 Entry 并刪除(將數組位置置為 null),釋放 Value 的引用。
  • 重要性remove() 是唯一能徹底清理無效 Entry 的方法,若未調用,Value 會一直被 Entry 強引用。

五、OOM 的觸發條件總結

結合源碼分析,ThreadLocal 導致 OOM 的核心條件如下:

條件描述
線程長期存活線程池中的線程不復用(如 FixedThreadPool),或線程未隨任務結束而銷毀。
存儲大對象任務中向 ThreadLocal 存儲大對象(如大數組、大集合),且未及時清理。
未主動調用 remove()開發者未在任務結束時調用 ThreadLocal.remove(),導致無效 Entry 持續累積。
清理機制未觸發線程存活期間未調用 get()set() 等方法,導致惰性清理未生效,無效 Entry 無法被回收。

六、避免 OOM 的最佳實踐

基于源碼和場景分析,避免 ThreadLocal 導致 OOM 的關鍵是 及時清理無效 Entry,具體措施如下:

1. 顯式調用 remove() 清理

在任務的 finally 塊中調用 ThreadLocal.remove(),確保無論任務是否異常,都能清理當前線程的 ThreadLocal 數據:

ExecutorService pool = Executors.newFixedThreadPool(1);
for (int i = 0; i < 100; i++) {pool.execute(() -> {try {byte[] bigData = new byte[10 * 1024 * 1024]; // 10MB 大對象threadLocal.set(bigData);} finally {threadLocal.remove(); // 關鍵:清理當前線程的 ThreadLocal 數據}});
}
2. 避免存儲大對象

盡量不在 ThreadLocal 中存儲大對象(如大數組、大集合)。若必須存儲,需評估對象生命周期,確保及時清理。

3. 合理選擇線程池類型
  • 對于短期任務(如 HTTP 請求處理),使用 CachedThreadPool(線程動態創建/銷毀),避免線程長期存活。
  • 對于長期任務(如定時任務),使用 FixedThreadPool 但嚴格清理 ThreadLocal 數據。
4. 監控與調優

通過內存分析工具(如 JProfiler、Arthas)監控 ThreadLocalMap 的內存占用,定位未清理的無效 Entry。


總結

ThreadLocal 導致 OOM 的根本原因是:線程池的線程長期存活,且任務中向 ThreadLocal 存儲了大對象但未及時清理,導致 ThreadLocalMap 中的無效 Entry 持續累積,最終耗盡內存

關鍵結論:

  • ThreadLocalMap 的 Entry 設計(弱引用 Key)無法自動回收 Value,必須依賴主動清理(remove())。
  • 線程池的線程復用特性會放大內存泄漏問題,需特別注意清理。
  • 顯式調用 remove() 是避免 OOM 的最有效手段

🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴

synchronized、volatile區別

synchronized 與 volatile 的深度解析(結合 JMM 與底層原理)

在 Java 并發編程中,synchronizedvolatile 是最常用的同步機制,但它們的設計目標、實現原理和應用場景有本質區別。本文將從 JMM(Java 內存模型) 出發,結合底層內存交互、指令重排、鎖優化等核心機制,系統對比兩者的差異,并通過代碼示例和場景分析說明其適用場景。


一、JMM 基礎:并發問題的底層根源

Java 內存模型(JMM)是 Java 虛擬機規范中對內存交互的抽象定義,它通過 主內存(Main Memory)線程本地內存(Working Memory) 的交互規則,解決了多線程環境下的 可見性、原子性、有序性 三大并發問題。

1. 主內存與線程本地內存
  • 主內存:所有線程共享的內存區域,存儲實例變量、靜態變量等共享數據(對應物理內存的一部分)。
  • 線程本地內存:每個線程私有的內存區域,存儲主內存中變量的副本(緩存)。線程通過“讀取主內存→本地計算→寫回主內存”的流程操作共享變量。
2. JMM 的三大并發問題
問題描述
可見性線程 A 修改了主內存中的變量,但線程 B 因本地緩存未刷新,無法立即看到最新值(如 flag 變量的延遲更新)。
原子性多線程并發修改共享變量時,操作可能被中斷(如 i++ 分解為“讀取→修改→寫入”三步),導致數據不一致。
有序性編譯器或處理器可能對指令重排序(優化性能),但單線程內重排序不影響結果(as-if-serial),多線程可能因重排序導致邏輯錯誤。

二、synchronized:互斥鎖與內存屏障的深度實現

synchronized 是 Java 的內置鎖機制,通過 監視器鎖(Monitor) 實現互斥訪問,其核心作用是保證 臨界區(Lock 包裹的代碼塊) 的原子性、可見性和有序性。

1. 實現原理:鎖的獲取與釋放

synchronized 的底層實現依賴 JVM 的 監視器鎖(Monitor) 和操作系統的 互斥量(Mutex),核心流程如下:

(1) 加鎖過程
  • 偏向鎖(優化):首次獲取鎖時,JVM 會記錄線程 ID(偏向該線程),后續該線程再次獲取鎖時無需原子操作(無競爭時性能極高)。
  • 輕量級鎖(優化):若偏向鎖被其他線程搶占,JVM 會通過 CAS(Compare-And-Swap)嘗試獲取鎖,避免直接升級為重量級鎖。
  • 重量級鎖(最終手段):若 CAS 失敗,線程會進入內核態,通過操作系統互斥量(Mutex)阻塞等待,直到鎖釋放。
(2) 釋放過程
  • 線程執行完臨界區代碼后,釋放 Monitor 鎖。
  • 內存屏障:釋放鎖前,JVM 會插入 StoreStore 屏障(禁止普通寫與 volatile 寫重排),并強制將本地內存的修改刷新到主內存(保證可見性)。
  • 線程釋放鎖后,其他線程競爭獲取鎖,獲取前會插入 LoadLoad 屏障(禁止 volatile 讀與后續讀重排),并從主內存加載最新值(保證可見性)。
2. 對 JMM 三大特性的支持
特性具體實現
可見性鎖釋放時強制刷新本地內存到主內存;鎖獲取時強制從主內存加載最新值(通過 StoreStoreLoadLoad 屏障)。
原子性臨界區代碼同一時間僅一個線程執行(互斥),保證復合操作(如 i++)的原子性。
有序性通過 happens-before 規則(鎖的釋放與獲取存在偏序關系),禁止跨鎖的指令重排(如臨界區內的代碼不會被重排到鎖外)。
3. 應用場景
  • 臨界區保護:多線程修改共享變量(如計數器 count++、狀態標志 isRunning)。
  • 方法同步:通過 synchronized 修飾方法(鎖是當前對象或類,如 public synchronized void method())。
  • 單例模式(DCL):防止多線程重復實例化(需配合 volatile 避免指令重排)。

三、volatile:輕量級可見性與禁止重排的底層機制

volatile 是輕量級的同步機制,僅作用于 變量級別,核心作用是保證變量的 可見性禁止指令重排,但不保證原子性。

1. 實現原理:主內存直連與內存屏障

volatile 的底層實現依賴 JVM 的 內存屏障(Memory Barrier),通過強制變量與主內存直接交互,避免線程本地緩存的延遲更新。

(1) 讀取過程
  • 線程讀取 volatile 變量時,直接從主內存獲取最新值(跳過本地緩存)。
  • JVM 插入 LoadLoad 屏障(禁止 volatile 讀與后續普通讀重排)和 LoadStore 屏障(禁止 volatile 讀與后續普通寫重排)。
(2) 寫入過程
  • 線程寫入 volatile 變量時,立即將值刷新到主內存(不等待本地緩存同步)。
  • JVM 插入 StoreStore 屏障(禁止普通寫與 volatile 寫重排)和 StoreLoad 屏障(禁止 volatile 寫與后續普通讀重排)。
(3) 禁止指令重排

通過內存屏障,volatile 變量的讀寫操作會被限制在特定的順序內,確保多線程下的邏輯正確性。例如:

// 以下兩行代碼不會被重排為 "b = 2; a = 1;"(若 a 是 volatile)
a = 1; 
b = 2;
2. 對 JMM 三大特性的支持
特性具體表現
可見性強制從主內存讀取和寫入,保證線程間變量值的實時同步(無本地緩存延遲)。
原子性僅保證單次讀/寫操作的原子性(如 int a = 1a = 1),但復合操作(如 a++)不保證(仍需 synchronized)。
有序性禁止編譯器和處理器對 volatile 變量的指令重排(通過內存屏障實現)。
3. 應用場景
  • 狀態標志:單線程修改、多線程讀取的布爾型變量(如 isRunningisShutdown)。
  • 單例模式(DCL):防止指令重排導致的空指針異常(需配合 synchronized 保證原子性)。
  • 輕量級通知:配合 wait/notify 實現線程間協作(但需結合 synchronized 使用)。

四、核心區別對比(表格+代碼示例)

維度synchronizedvolatile
作用范圍變量、方法、類(鎖對象)僅變量
可見性保證(鎖釋放刷主內存,鎖獲取讀主內存)保證(主內存直連)
原子性保證(臨界區互斥)不保證(僅單次讀/寫原子)
有序性保證(happens-before 規則)保證(禁止指令重排)
線程阻塞可能阻塞(多線程爭搶鎖時,進入內核態等待)不阻塞(無鎖機制,始終在用戶態執行)
性能開銷較高(鎖競爭、上下文切換,優化后輕量級鎖開銷低)較低(無鎖,僅內存屏障)
適用場景復合操作、臨界區保護(如計數器、狀態更新)狀態標志、單次讀寫、DCL
代碼示例 1:synchronized 保證原子性
public class SyncExample {private int count = 0;// synchronized 保證 count++ 的原子性public synchronized void increment() {count++; // 等價于 count = count + 1(讀取→修改→寫入三步)}public int getCount() {return count;}
}// 多線程測試:10 個線程各執行 1000 次 increment,最終 count 應為 10000
public static void main(String[] args) throws InterruptedException {SyncExample example = new SyncExample();ExecutorService pool = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {pool.execute(() -> {for (int j = 0; j < 1000; j++) {example.increment();}});}pool.shutdown();pool.awaitTermination(1, TimeUnit.MINUTES);System.out.println(example.getCount()); // 輸出 10000(正確)
}
  • 分析synchronized 保證 increment() 方法的互斥執行,避免了多線程并發修改導致的計數錯誤。
代碼示例 2:volatile 保證可見性但不保證原子性
public class VolatileExample {private volatile int count = 0; // volatile 保證可見性,但不保證原子性public void increment() {count++; // 非原子操作(讀取→修改→寫入)}public int getCount() {return count;}
}// 多線程測試:10 個線程各執行 1000 次 increment,最終 count 可能小于 10000
public static void main(String[] args) throws InterruptedException {VolatileExample example = new VolatileExample();ExecutorService pool = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {pool.execute(() -> {for (int j = 0; j < 1000; j++) {example.increment();}});}pool.shutdown();pool.awaitTermination(1, TimeUnit.MINUTES);System.out.println(example.getCount()); // 輸出可能小于 10000(錯誤)
}
  • 分析volatile 保證了 count 的可見性(線程能立即看到最新值),但 count++ 是復合操作(非原子),多線程并發時仍可能丟失更新。
代碼示例 3:volatile 禁止指令重排(DCL 單例模式)
public class Singleton {// volatile 禁止指令重排,防止多線程獲取到未初始化的對象private static volatile Singleton instance;private Singleton() {}// 雙重檢查鎖定(DCL)public static Singleton getInstance() {if (instance == null) { // 第一次檢查(無鎖)synchronized (Singleton.class) { // 加鎖if (instance == null) { // 第二次檢查(防競爭)instance = new Singleton(); // 關鍵:禁止重排}}}return instance;}
}
  • 分析instance = new Singleton()底層會分解為:
    1. 分配內存空間;
    2. 初始化對象;
    3. 將內存地址賦值給 instance(指針指向對象)。
      若未使用 volatile,編譯器可能重排為“1→3→2”,導致其他線程獲取到未初始化的對象(instance 不為 null,但對象未初始化)。volatile 通過內存屏障禁止此重排。

五、典型誤區與澄清

誤區 1:volatile 可以替代 synchronized
  • 錯誤volatile 無法保證原子性,無法替代 synchronized 處理復合操作(如 i++)。
  • 正確volatile 僅適用于單次讀寫場景(如狀態標志),復合操作需配合 synchronized 或使用 AtomicXXX 類(基于 CAS 保證原子性)。
誤區 2:synchronized 性能一定比 volatile 差
  • 錯誤:JVM 對無競爭的 synchronized 優化為 偏向鎖、輕量級鎖(用戶態 CAS 操作,無內核態切換),性能接近 volatile。僅在鎖競爭激烈時升級為重量級鎖(內核態阻塞),性能下降。
誤區 3:指令重排對單線程無影響
  • 正確:單線程內指令重排遵循 as-if-serial 規則(單線程執行結果與順序執行一致),但多線程可能因重排導致邏輯錯誤(如 DCL 未加 volatile)。

六、總結

  • synchronized 是“重量級”同步機制,通過鎖保證臨界區的原子性、可見性和有序性,適用于復合操作或需要互斥的場景。
  • volatile 是“輕量級”同步機制,通過主內存直連保證可見性和禁止指令重排,但不保證原子性,適用于狀態標志、單次讀寫等簡單場景。

選擇原則

  • 若需保證原子性(如計數器、狀態更新),用 synchronizedAtomicXXX
  • 若僅需保證可見性(如狀態標志),用 volatile
  • 復合操作(如 i++)需結合兩者(如 DCL 中 volatile 配合 synchronized)。

🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴

synchronized鎖粒度、模擬死鎖場景

一、synchronized 鎖粒度詳解

synchronized 是 Java 中最經典的同步機制,其核心是通過 監視器鎖(Monitor Lock) 實現對共享資源的互斥訪問。鎖的粒度(即鎖的作用范圍)決定了哪些操作會被同步,主要分為 對象鎖類鎖 兩種形式。

1. 對象鎖(Instance Lock)

對象鎖作用于 類的實例對象,確保同一時間只有一個線程能訪問該對象的同步代碼塊或同步方法。其核心是:每個 Java 對象都與一個內置的監視器(Monitor)綁定,線程進入同步代碼塊前需獲取該對象的 Monitor,退出時釋放。

(1)使用方式
  • 同步代碼塊:顯式指定鎖對象(通常是this或其他實例)。

    public class ObjectLockDemo {private final Object lock = new Object(); // 專用鎖對象(推薦)public void syncBlock() {synchronized (lock) { // 鎖是 lock 對象// 臨界區代碼}}public synchronized void syncMethod() { // 鎖是當前對象(this)// 臨界區代碼}
    }
    
  • 同步方法:默認鎖是當前對象(實例方法)或類的 Class 對象(靜態方法,見下文類鎖)。

(2)關鍵特性
  • 鎖的獨立性:不同實例對象的鎖相互獨立。例如,兩個不同的 ObjectLockDemo 實例的 syncMethod() 可以被不同線程同時執行。
  • 可重入性:同一線程可多次獲取同一對象的鎖(計數器遞增),避免自身死鎖。例如,遞歸調用同步方法時不會阻塞自己。
  • 鎖的釋放:鎖在同步代碼塊執行完畢或發生異常時自動釋放(通過 monitorexit 指令)。
2. 類鎖(Class Lock)

類鎖作用于 類的 Class 對象(每個類在 JVM 中僅有一個 Class 對象),確保同一時間只有一個線程能訪問該類的所有同步靜態方法或同步代碼塊(使用 類名.class 作為鎖)。

(1)使用方式
  • 同步靜態方法:默認鎖是類的 Class 對象。

    public class ClassLockDemo {public static synchronized void staticSyncMethod() {// 臨界區代碼(鎖是 ClassLockDemo.class)}
    }
    
  • 同步代碼塊(顯式指定類鎖)

    public class ClassLockDemo {public void syncClassBlock() {synchronized (ClassLockDemo.class) { // 鎖是類的 Class 對象// 臨界區代碼}}
    }
    
(2)關鍵特性
  • 全局唯一性:類鎖是類級別的,所有實例共享同一把鎖。例如,無論創建多少個 ClassLockDemo 實例,調用 staticSyncMethod() 都會被同步。
  • 與對象鎖互斥:類鎖和對象鎖是獨立的。例如,線程 A 持有對象鎖時,線程 B 仍可獲取類鎖(反之亦然)。
3. 鎖粒度的選擇
  • 對象鎖:適用于保護實例級別的共享資源(如實例變量)。
  • 類鎖:適用于保護靜態變量或全局共享資源(如單例模式中的實例創建)。

二、synchronized 的三大性質

1. 原子性(Atomicity)

原子性指一個操作或多個操作不可中斷,要么全部執行完成,要么全部不執行。

(1)synchronized 如何保證原子性?

synchronized 的底層通過 JVM 的 monitorentermonitorexit 指令實現:

  • monitorenter:線程嘗試獲取對象的 Monitor。若 Monitor 未被鎖定(計數器為 0),則獲取鎖并將計數器置為 1;若已被當前線程持有(計數器 > 0),則計數器遞增。
  • monitorexit:線程釋放鎖,計數器遞減。若計數器歸零,則釋放 Monitor。

這一過程保證了臨界區代碼的原子性,因為其他線程無法中斷當前線程對 Monitor 的持有。

(2)對比其他操作的原子性
  • 基本類型變量int a = 10 是原子操作(JVM 保證);但 a++(讀取-修改-寫入)不是原子操作。
  • long/double:在 32 位 JVM 上,longdouble 的讀寫可能被拆分為兩次 32 位操作(非原子),但 JVM 允許通過 -XX:+UseCompressedOops 等參數優化。
  • synchronized 的原子性范圍:覆蓋整個同步代碼塊,無論內部有多少操作。
2. 可見性(Visibility)

可見性指一個線程對共享變量的修改,其他線程能立即感知。

(1)synchronized 如何保證可見性?
  • 釋放鎖時刷新主內存:線程退出同步代碼塊(執行 monitorexit)前,會將所有修改的共享變量從工作內存刷新到主內存。
  • 獲取鎖時重載主內存:線程進入同步代碼塊(執行 monitorenter)前,會從主內存重新加載所有共享變量到工作內存,確保看到最新值。

這一機制通過 JVM 的內存屏障(Memory Barrier)實現,強制線程與主內存的同步。

(2)對比 volatile 的可見性
  • volatile 僅保證單個變量的可見性,且通過 lock 指令實現(與 synchronized 類似,但無鎖的獲取/釋放)。
  • synchronized 保證臨界區內所有變量的可見性,且能處理多個變量的復合操作(如 i++)。
3. 有序性(Ordering)

有序性指程序的執行順序與代碼編寫的順序一致(單線程內有序,多線程內可能重排序)。

(1)synchronized 如何保證有序性?

synchronized 通過 禁止編譯器/CPU 對同步代碼塊內的指令重排序 來保證有序性。具體通過內存屏障實現:

  • 在同步代碼塊的入口(monitorenter)插入 寫屏障(StoreStore Barrier),禁止普通寫與同步塊的寫重排序。
  • 在同步代碼塊的出口(monitorexit)插入 讀屏障(LoadLoad Barrier),禁止同步塊的讀與普通讀重排序。
(2)典型案例:雙重檢查鎖定(DCL)

單例模式中,若不使用 volatile 修飾實例變量,可能因指令重排序導致線程獲取未初始化的對象:

public class Singleton {private static Singleton instance; // 未加 volatile 時可能重排序public static Singleton getInstance() {if (instance == null) { // 第一次檢查synchronized (Singleton.class) {if (instance == null) { // 第二次檢查instance = new Singleton(); // 可能重排序為:分配內存 → 指向地址 → 初始化對象}}}return instance;}
}
  • 問題instance = new Singleton() 實際分為三步:
    1. 分配內存空間;
    2. 初始化對象;
    3. 將內存地址賦值給 instance(讓引用指向對象)。
      若步驟 2 和 3 重排序,線程 B 可能在 instance 不為空時(已指向地址但未初始化)直接使用,導致錯誤。
  • 解決:用 volatile 修飾 instance,禁止步驟 2 和 3 的重排序。但 synchronized 本身也能通過內存屏障禁止重排序,因此在同步塊內的操作是有序的。

三、死鎖的場景模擬與分析

死鎖(Deadlock)指兩個或多個線程互相持有對方需要的鎖,且無法繼續執行的狀態。

1. 死鎖的四個必要條件
  • 互斥條件:鎖一次只能被一個線程持有。
  • 持有并等待:線程持有至少一個鎖,并等待獲取其他線程持有的鎖。
  • 不可搶占:鎖只能被持有者主動釋放,不能被其他線程強行搶占。
  • 循環等待:線程間形成環狀等待鏈(線程 A 等待線程 B 的鎖,線程 B 等待線程 A 的鎖)。
2. 死鎖代碼示例

以下代碼構造了一個典型的死鎖場景:

// 類 E 和 E1 互相持有對方的鎖
class E {public static synchronized void methodE() throws InterruptedException {System.out.println(Thread.currentThread().getName() + " 進入 E.methodE");Thread.sleep(1000); // 模擬業務操作E1.methodE1(); // 請求 E1 的類鎖(靜態方法,鎖是 E1.class)}
}class E1 {public static synchronized void methodE1() throws InterruptedException {System.out.println(Thread.currentThread().getName() + " 進入 E1.methodE1");Thread.sleep(1000); // 模擬業務操作E.methodE(); // 請求 E 的類鎖(靜態方法,鎖是 E.class)}
}public class DeadLockDemo {public static void main(String[] args) {// 線程 1:先獲取 E 的類鎖,再請求 E1 的類鎖new Thread(() -> {try {E.methodE();} catch (InterruptedException e) {e.printStackTrace();}}, "Thread-1").start();// 線程 2:先獲取 E1 的類鎖,再請求 E 的類鎖new Thread(() -> {try {E1.methodE1();} catch (InterruptedException e) {e.printStackTrace();}}, "Thread-2").start();}
}
3. 死鎖現象

運行代碼后,輸出如下(程序卡住,無后續輸出):

Thread-1 進入 E.methodE
Thread-2 進入 E1.methodE1

此時,線程 1 持有 E.class 鎖并等待 E1.class 鎖,線程 2 持有 E1.class 鎖并等待 E.class 鎖,形成循環等待。

4. 死鎖的檢測與避免
(1)檢測死鎖
  • 工具檢測:使用 JDK 自帶的 jconsolejvisualvmjstack 工具查看線程狀態。例如,jstack <PID> 會輸出線程的堆棧信息,其中包含鎖的持有和等待關系。
  • 日志分析:在代碼中添加日志,記錄鎖的獲取和釋放順序,定位可能的循環等待。
(2)避免死鎖的方法
  • 固定加鎖順序:所有線程按相同的順序獲取鎖。例如,線程 1 和線程 2 都先獲取 E.class 鎖,再獲取 E1.class 鎖。
  • 使用超時機制:通過 Lock.tryLock(long timeout, TimeUnit unit) 替代 synchronized,設置超時時間,避免無限等待。
  • 減少鎖的嵌套:簡化同步邏輯,避免多個鎖的嵌套使用。
  • 鎖分離:使用讀寫鎖(ReentrantReadWriteLock),分離讀鎖和寫鎖,減少競爭。

四、總結

特性synchronizedvolatile
原子性保證同步代碼塊/方法的原子性(通過 Monitor 鎖)。僅保證基本類型(除 long/double)和引用類型的讀/寫原子性(依賴 JVM 實現)。
可見性釋放鎖時刷新主內存,獲取鎖時重載主內存(通過內存屏障)。強制變量從主內存讀取/寫入(通過 lock 指令)。
有序性禁止同步代碼塊內的指令重排序(通過內存屏障)。禁止指令重排序(通過 happens-before 規則)。
適用場景復雜臨界區(多步操作、多變量共享)。單一變量的可見性需求(如狀態標志)。

最佳實踐

  • 優先使用 volatile 解決可見性問題(簡單高效)。
  • 復雜同步邏輯使用 synchronized,并盡量縮小鎖的范圍(如使用專用鎖對象)。
  • 避免嵌套鎖,若必須使用則固定加鎖順序。
  • 死鎖發生時,通過工具(如 jstack)分析線程狀態,定位循環等待鏈。

🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴

Java并發和并行

Java 并發與并行:核心概念、區別與實踐

在 Java 編程中,并發(Concurrency)并行(Parallelism) 是兩個核心概念,用于描述多任務的處理方式。它們既有聯系又有本質區別,理解兩者的差異對設計高效的并發程序至關重要。

一、核心定義

1. 并發(Concurrency)

定義:多個任務在 同一時間間隔 內交替執行,宏觀上看起來“同時發生”,但微觀上同一時刻只有一個任務在執行(單處理器環境)。
?本質?:通過 ?任務切換? 實現“偽同時”,核心是解決任務的 ?調度與協調

示例
單核 CPU 上運行一個 Web 服務器,同時處理 10 個用戶的請求。CPU 會在 10 個請求之間快速切換(時間片輪轉),每個請求的響應看似“同時”完成,但實際是逐個處理的。

2. 并行(Parallelism)

定義:多個任務在 同一時刻 同時執行,依賴 多處理器/多核心 硬件支持。
?本質?:通過 ?物理資源的多線程執行? 實現真正的“同時”,核心是利用多核的計算能力。

示例
8 核 CPU 上運行 8 個線程,每個線程獨占一個核心,同時處理不同的計算任務(如大數據并行排序)。

二、關鍵區別

維度并發(Concurrency)并行(Parallelism)
核心目標解決任務的 調度與協調(如何高效切換任務)解決任務的 加速執行(如何利用多核資源)
硬件依賴單處理器即可實現(依賴時間片輪轉)必須依賴多處理器/多核心
執行方式微觀上單任務逐個執行(交替運行)微觀上多任務同時執行(物理并行)
典型場景IO 密集型任務(如 Web 服務器、數據庫連接池)CPU 密集型任務(如數值計算、圖像渲染)

三、Java 中的實現方式

1. 并發的實現:多線程與任務調度

Java 通過 多線程(Thread) 實現并發,核心機制是 操作系統的線程調度(時間片輪轉)。即使只有單核 CPU,Java 也能通過線程切換模擬“同時執行”。

關鍵工具

  • Thread 類:直接創建線程(new Thread().start())。
  • Runnable/Callable 接口:定義任務邏輯(Runnable 無返回值,Callable 有返回值)。
  • ExecutorService 線程池:管理線程生命周期,避免頻繁創建/銷毀線程的開銷(如 Executors.newFixedThreadPool(5))。

示例:單核下的并發(任務切換)

public class ConcurrencyDemo {public static void main(String[] args) {// 創建兩個任務Runnable task1 = () -> {for (int i = 0; i < 5; i++) {System.out.println("Task1: " + i);try { Thread.sleep(100); } catch (InterruptedException e) {}}};Runnable task2 = () -> {for (int i = 0; i < 5; i++) {System.out.println("Task2: " + i);try { Thread.sleep(100); } catch (InterruptedException e) {}}};// 單線程依次執行(非并發)// task1.run();// task2.run();// 多線程并發執行(單核下交替運行)new Thread(task1).start();new Thread(task2).start();}
}

輸出說明
單核環境下,兩個線程的輸出會交替出現(如 Task1:0Task2:0Task1:1Task2:1…),宏觀上“同時”執行,微觀上是 CPU 快速切換。

2. 并行的實現:多核與多線程

Java 利用 多核 CPU 實現并行,通過 Fork/Join 框架、并行流(Parallel Streams)或直接創建多線程(線程數 ≤ 核心數)實現任務的真正同時執行。

關鍵工具

  • ForkJoinPool:分治任務框架(如 RecursiveTask 遞歸拆分任務)。
  • 并行流(stream().parallel()):自動將任務分配到多核執行(底層基于 ForkJoinPool)。
  • 直接創建多線程(線程數等于核心數):每個線程綁定一個核心,避免上下文切換開銷。

示例:多核下的并行(同時執行)

import java.util.stream.IntStream;public class ParallelismDemo {public static void main(String[] args) {// 并行流:自動利用多核執行IntStream.range(0, 5).parallel() // 開啟并行.forEach(i -> {System.out.println("Parallel Task: " + i + " on Thread: " + Thread.currentThread().getName());try { Thread.sleep(100); } catch (InterruptedException e) {}});}
}

輸出說明
多核環境下,多個線程的輸出會同時出現(如 Parallel Task:0Parallel Task:1 可能同時打印),說明任務在多個核心上同時執行。

四、并發與并行的聯系

  1. 并行是并發的擴展:當并發的任務數超過 CPU 核心數時,系統會將部分任務分配到不同核心并行執行。例如,8 核 CPU 上運行 16 個線程,其中 8 個線程并行執行,另外 8 個線程并發等待。
  2. 并發是并行的基礎:并行需要先通過并發機制(如線程調度)將任務分配到不同核心,才能實現真正的同時執行。

五、挑戰與注意事項

1. 并發的挑戰
  • 線程安全:多個線程共享資源時可能出現競態條件(Race Condition),需通過 synchronizedLock 或原子類(AtomicInteger)保證原子性。
  • 上下文切換開銷:線程切換需要保存/恢復寄存器狀態,過多線程會導致性能下降(需控制線程數,如線程池大小)。
  • 死鎖/活鎖:線程間互相等待鎖時可能導致死鎖(需通過固定加鎖順序、超時機制避免)。
2. 并行的挑戰
  • 任務劃分:需將大任務拆分為獨立子任務(如分治算法),避免任務間的依賴(否則無法并行)。
  • 負載均衡:子任務計算量需盡量均衡,避免某些核心空閑(如 ForkJoinPool 的工作竊取機制)。
  • 資源競爭:多核同時訪問共享資源時仍需同步(如并行流中修改共享變量需謹慎)。

六、總結

維度并發(Concurrency)并行(Parallelism)
核心任務交替執行(單核模擬“同時”)任務真正同時執行(多核物理并行)
Java 實現多線程、線程池(ExecutorService并行流、ForkJoinPool、多線程(線程數=核心數)
適用場景IO 密集型(如 Web 服務器、數據庫交互)CPU 密集型(如數值計算、大數據處理)
關鍵問題線程安全、上下文切換、死鎖任務劃分、負載均衡、資源競爭

最佳實踐

  • IO 密集型任務優先用并發(減少線程等待時間)。
  • CPU 密集型任務優先用并行(充分利用多核性能)。
  • 避免過度設計:單核環境下無需強行并行,并發調度已足夠高效。

🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴

怎么提高并發量,請列舉你所知道的方案?

要系統性地提高系統的并發處理能力,需從 資源效率、請求鏈路優化、架構擴展性 三個核心維度展開,每個維度包含多個技術點,且需結合具體業務場景選擇組合方案。以下是更深入的技術細節和落地實踐:

一、靜態資源優化:降低動態請求壓力

靜態資源(HTML、CSS、JS、圖片、視頻)的優化是提升并發的“低門檻高收益”手段,核心目標是 減少服務器計算、降低網絡帶寬消耗

1. HTML 靜態化:從動態生成到預渲染
  • 原理:將動態渲染的頁面(如用戶個人中心、商品詳情頁)提前生成靜態 HTML 文件,用戶直接訪問靜態文件,避免服務器每次請求都執行數據庫查詢和模板渲染。
  • 實現方式
    • CMS 系統自動生成:使用 WordPress、Drupal 等 CMS 系統,通過“發布”操作自動生成靜態 HTML(如 WordPress 的 wp-content/cache 目錄)。
    • 定時任務生成:對低頻更新頁面(如首頁、活動頁),通過 Quartz 或 Linux Crontab 定時調用渲染接口生成靜態文件(示例:每天凌晨 3 點生成首頁 index.html)。
    • 動態轉靜態中間件:使用 Nginx 的 ngx_http_rewrite_module 或 OpenResty 的 Lua 腳本,將動態 URL(如 /article/123)映射到靜態文件(/data/html/article_123.html)。
  • 效果:某新聞網站將首頁從動態渲染改為靜態化后,服務器 CPU 使用率從 80% 降至 30%,響應時間從 500ms 縮短至 50ms。
2. 圖片/靜態資源分離與 CDN 加速
  • 圖片服務器獨立
    • 架構設計:主服務器(如 Nginx)僅返回圖片 URL(如 https://img.example.com/photo.jpg),用戶直接訪問獨立圖片服務器(如 img.example.com)或 CDN 節點。
    • 性能優化:圖片服務器關閉不必要的模塊(如 Apache 的 mod_rewrite),僅保留靜態文件服務;使用 sendfile 系統調用(Nginx 配置 sendfile on;)減少用戶態到內核態的拷貝。
  • CDN 深度集成
    • 選型:根據業務需求選擇云 CDN(如阿里云 CDN、Cloudflare)或專用 CDN(如 Akamai)。
    • 配置步驟
      1. 將靜態資源(圖片、JS、CSS)上傳至 CDN 源站(如阿里云 OSS)。
      2. 在 CDN 控制臺配置緩存規則(如圖片緩存 30 天,JS 緩存 7 天)。
      3. 開啟智能壓縮(如 Brotli 壓縮,壓縮率可達 20%~30%)。
      4. 配置回源策略(如優先從源站拉取,緩存過期后異步更新)。
    • 效果:某電商網站使用 CDN 后,圖片加載時間從 800ms 降至 200ms,源站帶寬成本降低 60%。
3. 靜態資源緩存策略:多層防護
  • 瀏覽器緩存:通過 HTTP 頭控制緩存行為(示例 Nginx 配置):

    location /static/ {expires 30d; # 靜態資源緩存 30 天add_header Cache-Control "public, max-age=2592000";
    }
    
  • 反向代理緩存(Nginx):對未命中的靜態資源回源到源站,并緩存到本地(示例):

    proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=my_cache:10m inactive=60m;
    server {location /static/ {proxy_pass http://source_server;proxy_cache my_cache;proxy_cache_valid 200 30d; # 200 響應緩存 30 天}
    }
    
  • CDN 緩存:CDN 節點緩存靜態資源,通過 Cache-Controlstale-while-revalidate 策略平衡實時性與性能(如 Cloudflare 的“Cache Everything”規則)。

二、動態請求處理:提升服務器吞吐量

動態請求(如用戶登錄、下單、查詢數據庫)需服務器實時計算,優化方向包括 負載均衡、應用服務器調優、異步化

1. 負載均衡:分散流量的核心樞紐
  • 硬件負載均衡(F5/A10)

    • 原理:基于四層(TCP/UDP)或七層(HTTP)協議,將請求按算法(輪詢、加權輪詢、IP 哈希)分配到后端服務器。
    • 適用場景:超大規模流量(如單集群 10 萬+ QPS),需硬件級性能保障(F5 最大可處理 200Gbps 流量)。
    • 配置示例:在 F5 中配置虛擬服務器(VIP)指向后端應用服務器集群,設置健康檢查(如 HTTP 200 響應)自動剔除故障節點。
  • 軟件負載均衡(LVS/Nginx)

    • LVS(四層):基于內核模塊ip_vs實現,支持 NAT、DR、TUN 模式(示例 DR 模式配置):

      # LVS 主節點配置(/etc/sysconfig/ipvsadm)
      IPVSADM='/sbin/ipvsadm'
      $IPVSADM -A -t 192.168.1.100:80 -s rr # 添加虛擬服務,輪詢算法
      $IPVSADM -a -t 192.168.1.100:80 -r 192.168.1.101:80 -m # 添加后端節點 1
      $IPVSADM -a -t 192.168.1.100:80 -r 192.168.1.102:80 -m # 添加后端節點 2
      
    • Nginx(七層):基于 HTTP 協議,支持更靈活的路由規則(如按 URL 路徑、Cookie 分發):

      http {upstream app_servers {server 192.168.1.101:8080 weight=3; # 權重 3,承擔 3/4 流量server 192.168.1.102:8080 weight=1;}server {listen 80;location / {proxy_pass http://app_servers;}}
      }
      
  • 云廠商負載均衡(阿里云 SLB/AWS ALB)

    • 優勢:集成健康檢查(如 TCP 檢查、HTTP 檢查)、自動擴縮容(根據 CPU 使用率自動增減后端實例)、SSL 卸載(減少后端服務器加密開銷)。
    • 適用場景:云原生架構,無需自建負載均衡集群。
2. 應用服務器優化:提升單節點處理能力
  • 線程池調優

    • Tomcat 線程池參數(server.xml):

      <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"maxThreads="200"       # 最大工作線程數(建議為 CPU 核心數×2)minSpareThreads="50"   # 最小空閑線程數(提前創建)acceptCount="100"      # 請求隊列長度(超出則拒絕)connectionTimeout="20000"/> # 連接超時時間(ms)
      
      • 經驗值:CPU 密集型應用(如計算服務)maxThreads 設為 CPU 核心數×1;IO 密集型應用(如數據庫查詢)設為 CPU 核心數×2~4。
    • Jetty 線程池:通過 qtp-* 線程池控制,建議配置 maxThreads=200acceptors=4(與 CPU 核心數相關)。

  • 異步處理:釋放主線程

    • Servlet 3.0 異步支持:通過AsyncContext將耗時操作轉移到后臺線程(示例):

      @WebServlet(urlPatterns = "/async", asyncSupported = true)
      public class AsyncServlet extends HttpServlet {protected void doGet(HttpServletRequest req, HttpServletResponse resp) {AsyncContext asyncCtx = req.startAsync();asyncCtx.setTimeout(30000); // 超時時間 30sexecutor.submit(() -> {try {// 耗時操作(如調用外部 API)String result = callExternalAPI();asyncCtx.getResponse().setCharacterEncoding("UTF-8");asyncCtx.getResponse().getWriter().write(result);} finally {asyncCtx.complete();}});}
      }
      
    • Spring 異步(@Async):通過自定義線程池處理耗時方法(示例):

      @Service
      public class OrderService {@Autowiredprivate OrderRepository orderRepo;@Async("orderExecutor") // 使用自定義線程池public CompletableFuture<Void> sendNotification(Long orderId) {Order order = orderRepo.findById(orderId).orElseThrow();smsClient.send(order.getUserPhone(), "訂單已支付");return CompletableFuture.completedFuture(null);}
      }// 配置自定義線程池
      @Configuration
      @EnableAsync
      public class AsyncConfig {@Bean("orderExecutor")public Executor orderExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(50);   // 核心線程數executor.setMaxPoolSize(200);   // 最大線程數executor.setQueueCapacity(1000);// 任務隊列容量executor.setThreadNamePrefix("Order-Async-");executor.initialize();return executor;}
      }
      
  • 無狀態設計:應用服務器不存儲會話(Session),通過 Redis 或 Memcached 集中管理(示例 Spring Session 配置):

    # application.properties
    spring.session.store-type=redis
    spring.redis.host=redis.example.com
    spring.redis.port=6379
    
    • 效果:支持水平擴展,新增應用服務器無需同步會話數據。
3. 異步化與消息隊列:削峰填谷
  • 消息隊列選型

    • Kafka:高吞吐量(百萬級 TPS),適合日志收集、大數據流處理(如 Flink 實時計算)。
    • RocketMQ:支持事務消息(如訂單支付與庫存扣減的一致性),適合電商場景。
    • RabbitMQ:支持多種消息模型(直連、廣播),適合小規模異步任務(如通知推送)。
  • 典型流程

    1. 用戶發起請求(如下單)。
    2. 應用服務器將核心操作(如扣減庫存)寫入消息隊列。
    3. 主線程返回“下單成功”,釋放連接。
    4. 消費者從隊列拉取消息,執行耗時操作(如發送短信、更新物流)。
  • 代碼示例(Spring Kafka)

    // 生產者:下單后發送消息
    @Service
    public class OrderService {@Autowiredprivate KafkaTemplate<String, Order> orderKafkaTemplate;public void createOrder(Order order) {// 1. 扣減庫存(同步)inventoryService.deductStock(order.getProductId());// 2. 寫入數據庫(同步)orderRepo.save(order);// 3. 發送異步消息(通知物流、積分)orderKafkaTemplate.send("order_topic", order);}
    }// 消費者:處理物流通知
    @KafkaListener(topics = "order_topic", groupId = "logistics_group")
    public void handleLogistics(Order order) {logisticsService.sendNotification(order); // 耗時操作(如調用物流 API)
    }
    

三、數據庫優化:解決單點瓶頸

數據庫是大多數系統的性能瓶頸,優化方向包括 分庫分表、讀寫分離、索引優化、緩存加速

1. 分庫分表:分散數據存儲
  • 垂直分庫

    • 原理:按業務模塊拆分數據庫(如用戶庫 user_db、訂單庫 order_db、商品庫 product_db)。

    • 實現方式

      • 應用層路由:代碼中根據業務類型選擇數據庫(如 userMapper 連接 user_db)。

      • 中間件代理:ShardingSphere 自動路由(示例配置):

        # sharding-jdbc 配置(application.yml)
        spring:shardingsphere:datasources:ds0:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://mysql0.example.com:3306/user_dbds1:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://mysql1.example.com:3306/order_dbrules:database:sharding-column: user_idsharding-algorithm-name: db_inlinetables:order_table:actual-data-nodes: ds$->{0..1}.order_$->{0..1}database-strategy:standard:sharding-column: user_idsharding-algorithm-name: db_inlinetable-strategy:standard:sharding-column: order_idsharding-algorithm-name: table_inline
        
  • 水平分表

    • 原理:將大表按規則(如用戶 ID 取模、時間范圍)拆分為多個子表(如 order_0order_1)。
    • 分片鍵選擇:優先使用高頻查詢字段(如 user_id),避免跨分片查詢(如 WHERE user_id=123 只訪問 order_123%10=3 的表)。
    • 實現工具:ShardingSphere(Java)、DRDS(阿里云)、MyCat(開源)。
  • 效果:某電商訂單表單表數據量 5000 萬行,QPS 8000,分表(10 張)后單表 500 萬行,QPS 提升至 2 萬。

2. 讀寫分離:分擔主庫壓力
  • 主從復制

    • MySQL 主從復制:基于 Binlog 同步(示例配置):

      # 主庫 my.cnf
      server-id=1
      log-bin=mysql-bin
      binlog-format=ROW# 從庫 my.cnf
      server-id=2
      relay-log=relay-bin
      read-only=1 # 從庫只讀
      
    • 同步延遲監控:通過 SHOW SLAVE STATUS 查看 Seconds_Behind_Master(正常應 < 1s)。

  • 中間件代理

    • MyCat:配置讀寫分離規則(示例schema.xml):

      <schema name="order_schema"><table name="order_table" dataNode="dn1,dn2"/>
      </schema>
      <dataNode name="dn1" dataHost="master_host" database="order_db"/>
      <dataNode name="dn2" dataHost="slave_host" database="order_db_slave"/>
      <dataHost name="master_host" maxCon=100 minCon=10 balance="0"><writeHost host="master" url="mysql://master_user:password@master_ip:3306"/>
      </dataHost>
      <dataHost name="slave_host" maxCon=100 minCon=10 balance="1"><readHost host="slave" url="mysql://slave_user:password@slave_ip:3306"/>
      </dataHost>
      
      • balance="0":所有讀請求到寫庫(主庫);balance="1":讀請求隨機到從庫。
  • 應用層控制:在代碼中區分讀寫操作(如 MyBatis 攔截器):

    @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
    })
    public class ReadWriteSplitInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement ms = (MappedStatement) invocation.getArgs()[0];String methodName = ms.getId();if (methodName.contains("select")) { // 查詢操作路由到從庫return invocation.proceedWithSlave();} else { // 寫操作路由到主庫return invocation.proceedWithMaster();}}
    }
    
3. 索引與 SQL 優化
  • 索引設計原則

    • 覆蓋索引:查詢所需字段全部包含在索引中(如 SELECT id, name FROM user WHERE age=20,創建 (age, name) 索引)。
    • 復合索引順序:高頻條件字段在前(如 WHERE user_id=? AND status=?,索引 (user_id, status)(status, user_id) 更高效)。
    • 避免冗余索引:定期使用 SHOW INDEX FROM table 檢查并刪除重復索引。
  • 慢 SQL 治理

    • 定位慢 SQL:開啟 MySQL 慢查詢日志(slow_query_log=ONlong_query_time=1)。

    • 分析執行計劃:使用EXPLAIN查看索引是否命中、是否全表掃描(示例):

      EXPLAIN SELECT * FROM order_table WHERE user_id=123 AND create_time > '2024-01-01';
      
      • 關注 type(理想值為 refeq_ref,避免 ALL 全表掃描)、key(實際使用的索引)。
  • 鎖優化

    • 樂觀鎖:使用版本號(version字段)避免悲觀鎖(示例):

      UPDATE product SET stock=stock-1, version=version+1 WHERE id=123 AND version=old_version;
      
    • 減少事務時長:將非必要操作移到事務外(如日志記錄)。

四、緩存策略:減少重復計算與數據庫訪問

緩存是提升并發的核心手段,通過存儲高頻數據副本,避免重復計算或數據庫查詢。

1. 本地緩存(進程內緩存)
  • Guava Cache

    • 核心特性:基于 LRU 淘汰策略,支持容量限制、過期時間(expireAfterAccess/expireAfterWrite)。

    • 示例代碼

      Cache<Long, User> userCache = CacheBuilder.newBuilder().maximumSize(1000) // 最大容量 1000.expireAfterAccess(30, TimeUnit.MINUTES) // 30 分鐘無訪問則過期.build();// 讀取緩存(未命中則查數據庫)
      User user = userCache.get(userId, () -> userDao.getUserById(userId));
      
  • Caffeine

    • 優勢:比 Guava 更快(基于 W-TinyLFU 算法),支持基于權重的淘汰、刷新策略。

    • 示例配置

      Cache<Long, User> userCache = Caffeine.newBuilder().maximumSize(1000).expireAfterWrite(30, TimeUnit.MINUTES).refreshAfterWrite(5, TimeUnit.MINUTES) // 5 分鐘后自動刷新.build(key -> userDao.getUserById(key));
      
2. 分布式緩存(跨進程緩存)
  • Redis 核心操作

    • 字符串(String):存儲單個值(如用戶會話 user:123:info)。
    • 哈希(Hash):存儲對象(如 user:123 對應 {name: "張三", age: 25})。
    • 列表(List):存儲隊列(如消息隊列 mq:order)。
    • 集合(Set):存儲標簽(如 tag:hot 存儲熱門商品 ID)。
    • 有序集合(ZSet):存儲排行榜(如 rank:sales 按銷量排序)。
  • 緩存穿透

    • 問題:查詢不存在的數據(如 user_id=-1),導致每次請求都查數據庫。

    • 解決方案

      • 緩存空值:查詢結果為空時,緩存 null(設置短過期時間,如 5min)。

      • 布隆過濾器(Bloom Filter):預先存儲所有存在的user_id,查詢前判斷是否存在(示例 Redisson 布隆過濾器):

        RBloomFilter<Long> bloomFilter = redisson.getBloomFilter("user_bloom_filter");
        bloomFilter.tryInit(1000000L, 0.01); // 預計插入 100 萬條,誤判率 1%
        for (Long userId : allUserIds) {bloomFilter.add(userId);
        }
        if (!bloomFilter.contains(userId)) {return null; // 不存在,直接返回
        }
        
  • 緩存擊穿

    • 問題:熱點 key(如 product:123)過期時,大量請求同時查數據庫。

    • 解決方案

      • 永不過期:設置邏輯過期時間(如記錄 expire_time 字段,查詢時判斷是否過期)。

      • 互斥鎖(Mutex):使用 RedisSETNX鎖,僅允許一個線程查數據庫(示例):

        String lockKey = "lock:user:123";
        if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS)) {try {// 查數據庫并更新緩存} finally {redisTemplate.delete(lockKey);}
        } else {// 重試或返回舊值
        }
        
  • 緩存雪崩

    • 問題:大量 key 同時過期,導致數據庫壓力驟增。
    • 解決方案:為不同 key 設置隨機過期時間(如 30min~1h)。
3. 多級緩存架構
  • 流程設計

    1. 用戶請求 → 本地緩存(Caffeine):命中則返回。
    2. 未命中 → 分布式緩存(Redis):命中則返回,并回種本地緩存。
    3. 未命中 → 數據庫:查詢后回種 Redis 和本地緩存。
  • 代碼示例

    public User getUser(Long userId) {// 1. 查本地緩存User user = localCache.getIfPresent(userId);if (user != null) {return user;}// 2. 查分布式緩存user = redisTemplate.opsForValue().get("user:" + userId);if (user != null) {localCache.put(userId, user); // 回種本地緩存return user;}// 3. 查數據庫user = userDao.getUserById(userId);if (user != null) {redisTemplate.opsForValue().set("user:" + userId, user, 30, TimeUnit.MINUTES); // 回種 RedislocalCache.put(userId, user); // 回種本地緩存}return user;
    }
    

五、異步與消息隊列:解耦耗時操作

通過消息隊列將非實時操作(如日志、通知)異步處理,釋放主線程處理新請求。

1. 消息隊列選型對比
特性KafkaRocketMQRabbitMQ
吞吐量百萬級 TPS(順序寫磁盤)十萬級 TPS(支持事務)萬級 TPS(基于內存)
消息順序分區內有序全局有序(單分區)不保證全局有序
消息可靠性至少一次(需消費者確認)至少一次(支持事務回滾)至少一次(支持死信隊列)
適用場景日志收集、大數據流處理電商交易、金融支付小規模通知、任務調度
2. Kafka 高級實踐
  • 分區與副本

    • 分區數:根據消費者數量設置(如 3 個消費者設 3 個分區,提高并行度)。
    • 副本數:設置為 3(主副本 + 2 個從副本),防止單節點故障。
  • 消費者組

    • 廣播消費:每個消費者接收全量消息(enable.auto.commit=false,手動提交偏移量)。
    • 集群消費:消息被組內一個消費者消費(默認模式)。
  • 示例生產者

    Properties props = new Properties();
    props.put("bootstrap.servers", "kafka1.example.com:9092,kafka2.example.com:9092");
    props.put("acks", "all"); // 所有副本確認
    props.put("retries", 3); // 重試次數
    props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
    props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");KafkaProducer<String, String> producer = new KafkaProducer<>(props);
    ProducerRecord<String, String> record = new ProducerRecord<>("order_topic", "key", JSON.toJSONString(order));
    producer.send(record, (metadata, exception) -> {if (exception != null) {// 處理發送失敗(如記錄日志、重試)}
    });
    producer.close();
    
3. RocketMQ 事務消息
  • 原理:通過兩階段提交(2PC)保證消息與數據庫操作的原子性。

  • 流程

    1. 發送半消息(PREPARED 狀態,消費者不可見)。
    2. 執行本地事務(如扣減庫存)。
    3. 根據事務結果提交(COMMIT)或回滾(ROLLBACK)消息。
  • 示例代碼

    TransactionMQProducer producer = new TransactionMQProducer("order_producer_group");
    producer.setNamesrvAddr("rocketmq-namesrv:9876");
    producer.start();TransactionSendResult result = producer.sendMessageInTransaction(new Message("order_topic", "TAG_A", JSON.toJSONString(order).getBytes()),new LocalTransactionExecutor() {@Overridepublic LocalTransactionState executeLocalTransactionBranch(Message msg, Object arg) {// 執行本地事務(扣減庫存)boolean success = inventoryService.deductStock(msg.getProperty("productId"));return success ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE;}},null
    );
    

六、限流降級與容災:保護系統穩定性

高并發下需防止系統過載,通過限流、降級、熔斷等機制保障核心業務可用。

1. 限流(Rate Limiting)
  • Sentinel 滑動窗口算法

    • 原理:將時間窗口劃分為多個小窗口(如 1 秒分為 10 個 100ms 窗口),統計每個小窗口的請求數,滑動窗口平滑流量。

    • 示例配置(控制臺定義規則):

      {"resource": "order_api","limitApp": "default","grade": 1, // 1=QPS 限流,0=線程數限流"count": 1000, // 每秒最多 1000 次請求"timeWindow": 1, // 時間窗口 1 秒"strategy": 0, // 0=直接拒絕,1=Warm Up,2=排隊等待"controlBehavior": 0
      }
      
    • 集成 Spring Boot

      @RestController
      public class OrderController {@GetMapping("/order")@SentinelResource(value = "order_api", blockHandler = "handleBlock")public String createOrder() {return "下單成功";}public String handleBlock(BlockException ex) {return "當前流量過大,請稍后再試";}
      }
      
2. 降級(Degradation)
  • Sentinel 閾值降級

    • 原理:監控服務的錯誤率(如 > 50%)或平均響應時間(如 > 3s),觸發降級(返回默認值或空)。

    • 示例配置(控制臺定義規則):

      {"resource": "payment_service","grade": 0, // 0=錯誤率降級,1=平均響應時間降級"count": 50, // 錯誤率閾值 50%"timeWindow": 10, // 統計時間窗口 10 秒"minRequestAmount": 5 // 最小請求數(避免偶發波動)
      }
      
  • Hystrix 熔斷降級(已停更,僅作參考)

    @HystrixCommand(fallbackMethod = "fallbackPay",commandProperties = {@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 10 秒內至少 10 次請求@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"), // 錯誤率 > 50% 觸發熔斷@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000") // 熔斷 5 秒后嘗試恢復}
    )
    public String pay(Order order) {return paymentService.pay(order); // 調用支付服務
    }public String fallbackPay(Order order) {return "支付服務繁忙,請稍后再試";
    }
    
3. 熔斷(Circuit Breaker)
  • Resilience4J 熔斷(Sentinel 的替代方案)

    • 核心狀態:CLOSED(正常)、OPEN(熔斷)、HALF_OPEN(半開,嘗試恢復)。

    • 示例代碼

      CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("paymentService");
      CheckedFunction0<String> decoratedSupplier = CircuitBreaker.decorateCheckedSupplier(circuitBreaker, () -> paymentService.pay(order));
      Try<String> result = Try.of(decoratedSupplier).recover(ex -> "支付失敗:" + ex.getMessage());
      

七、云原生與彈性伸縮:應對流量波動

云原生技術通過容器化、自動化擴縮容,靈活應對流量高峰與低谷。

1. 容器化(Docker/K8s)
  • Docker 鏡像構建

    • 多階段構建:減小鏡像體積(示例Dockerfile):

      # 構建階段
      FROM maven:3.8.6 AS builder
      WORKDIR /app
      COPY pom.xml .
      RUN mvn dependency:go-offline
      COPY src ./src
      RUN mvn package -DskipTests# 運行階段
      FROM openjdk:17-jdk-slim
      WORKDIR /app
      COPY --from=builder /app/target/app.jar .
      EXPOSE 8080
      CMD ["java", "-jar", "app.jar"]
      
  • Kubernetes 彈性伸縮

    • HPA(Horizontal Pod Autoscaler):基于 CPU/內存或自定義指標(如 QPS)自動擴縮 Pod 數量(示例):

      apiVersion: autoscaling/v2
      kind: HorizontalPodAutoscaler
      metadata:name: app-hpa
      spec:scaleTargetRef:apiVersion: apps/v1kind: Deploymentname: app-deploymentminReplicas: 2maxReplicas: 10metrics:- type: Resourceresource:name: cputarget:type: UtilizationaverageUtilization: 70 # CPU 使用率超 70% 時擴容
      
2. Serverless(無服務器)
  • AWS Lambda

    • 適用場景:突發流量(如秒殺活動)、定時任務(如每日數據統計)。

    • 示例代碼(Python)

      import jsondef lambda_handler(event, context):# 處理秒殺請求item_id = event['queryStringParameters']['itemId']stock = get_stock_from_dynamodb(item_id) # 調用 DynamoDBif stock > 0:deduct_stock(item_id) # 扣減庫存return {'statusCode': 200,'body': json.dumps('秒殺成功')}else:return {'statusCode': 400,'body': json.dumps('庫存不足')}
      
  • 阿里云函數計算(FC)

    • 優勢:與阿里云其他服務(OSS、RDS)深度集成,支持事件觸發(如 OSS 文件上傳觸發函數)。

總結:高并發系統的組合策略

高并發系統需根據業務場景 多維度優化,以下是常見場景的最佳實踐:

場景核心方案
靜態內容為主(新聞網站)HTML 靜態化 + CDN 加速 + 圖片服務器分離 + 瀏覽器緩存
動態交互為主(電商)負載均衡(Nginx/LVS) + 數據庫分庫分表 + 分布式緩存(Redis) + 異步消息隊列(Kafka)
突發流量(秒殺)限流降級(Sentinel) + 彈性伸縮(K8s/Serverless) + 本地緩存(Caffeine) + 消息隊列削峰
高一致性要求(金融)分布式事務(Seata) + 緩存一致性(雙寫+失效) + 數據庫主從復制 + 讀寫分離

關鍵原則

  • 優先通過緩存、異步、靜態化減少動態請求;
  • 數據庫是瓶頸,需盡早分庫分表;
  • 監控(Prometheus + Grafana)和壓測(JMeter)是優化的前提,需持續觀察系統瓶頸。

🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴🔴🟠🟡🟢🔵🟣🔴

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/913781.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/913781.shtml
英文地址,請注明出處:http://en.pswp.cn/news/913781.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

網關助力航天噴涂:Devicenet與Modbus TCP的“跨界對話“

在航空航天領域&#xff0c;飛機、航天器的制造過程有著極高的精度與安全性要求。以飛機、航天器表面噴涂作業為例&#xff0c;不僅要進行嚴格的防腐蝕處理&#xff0c;而且對表面光滑度要求極高&#xff0c;這直接關系到飛行器的空氣動力學性能和使用壽命。為確保作業安全與質…

從傳統項目管理到敏捷DevOps:如何轉向使用DevOps看板工具進行工作流管理

在DevOps實踐中&#xff0c;DevOps看板工具成為了開發與運維團隊之間高效協作的關鍵。隨著企業對敏捷開發和持續交付的需求日益增長&#xff0c;DevOps看板工具通過可視化的管理方法&#xff0c;幫助團隊在繁雜的任務中保持高效的工作節奏和清晰的進度跟蹤。 具體而言&#xff…

【leetcode100】下一個排列

1、題目描述 整數數組的一個 排列 就是將其所有成員以序列或線性順序排列。 例如&#xff0c;arr [1,2,3] &#xff0c;以下這些都可以視作 arr 的排列&#xff1a;[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。 整數數組的 下一個排列 是指其整數的下一個字典序更大的排列。更正…

Flink-Source算子狀態恢復分析

背景 修改 source 算子 kafka_old_topic 消費任務運行一段時間后&#xff0c;暫停狀態并保留。然后將 uid 和 topic 都改了&#xff0c;消費者 offset 會從 earliest 開始。 // before FlinkKafkaConsumer consumer KafkaConfig.getConsumer("kafka_old_topic");…

IDEA中application.yml配置文件不自動提示解決辦法

今天在自己的電腦上使用IDEA的時候&#xff0c;發現在application配置文件里面輸入配置項的時候沒有提示&#xff0c;網上找了一圈也沒解決&#xff0c;最后自己試出來了。 解決辦法&#xff1a; 鼠標移動到配置文件上&#xff0c;單擊右鍵-重寫文件類型、選擇YAML(捆綁)&#…

Vite 完整功能詳解與 Vue 項目實戰指南

Vite 完整功能詳解與 Vue 項目實戰指南 Vite 是下一代前端開發工具&#xff0c;由 Vue 作者尤雨溪開發&#xff0c;提供極速的開發體驗和高效的生產構建。以下是完整功能解析和實戰示例&#xff1a;一、Vite 核心功能亮點閃電般冷啟動 基于原生 ES 模塊&#xff08;ESM&#xf…

Vue 3 中使用路由參數跳轉時 watch 觸發重復請求問題詳解

&#x1f4d8;Vue 3 中使用路由參數跳轉時 watch 觸發重復請求問題詳解&#x1f516; 收藏 點贊 關注&#xff0c;掌握 Vue 3 路由參數監聽中的隱藏陷阱&#xff0c;避免詳情頁、嵌套路由頁誤觸發重復請求&#xff01;&#x1f9e9; 一、問題背景 在 Vue 3 項目中&#xff0c…

前端 項目更新通知 (plugin-web-update-notification)

項目版本更新迭代時&#xff0c;需提示用戶更新系統&#xff0c;不然早時間不更新對用戶體驗很不好&#xff0c;所以在每次部署后需要提示用戶&#xff0c;刷新靜態資源。推薦插件 plugin-web-update-notification .具體配置 vite.config.js文件中 import { webUpdateNotice …

【力扣(LeetCode)】數據挖掘面試題0002:當面對實時數據流時您如何設計和實現機器學習模型?

文章大綱一、實時數據處理&#xff1a;構建低延遲的數據管道1. 數據接入與緩沖2. 實時清洗與校驗3. 特征標準化與對齊二、模型設計&#xff1a;選擇適配實時場景的模型架構1. 模型選擇原則三、訓練與更新策略&#xff1a;離線與在線協同&#xff0c;應對概念漂移1. 離線-在線協…

TongWeb8.0.9.0.3部署后端應用,前端訪問后端報405(by sy+lqw)

問題描述&#xff1a; 客戶前端部署在nginx上&#xff0c;后端部署在tongweb8上&#xff08;相當于前后端分離&#xff09;&#xff0c;登錄的時候報錯&#xff0c;f12看network&#xff0c;狀態碼405&#xff0c;如下所示&#xff1a;看console&#xff0c;如下所示&#xff1…

mysql互為主從失效,重新同步

一、分別登錄服務器A和服務器B的mysqlmysql -u root -p 123456789二、分別查看數據庫狀態信息,下邊兩項參數有一項為NO就表示同步異常Slave_IO_Running:從服務器&#xff08;Slave&#xff09;中的 I/O 線程的運行狀態Slave_SQL_Running:從服務器上的 SQL 線程是否正在運行mysq…

板凳-------Mysql cookbook學習 (十一--------6)

https://blog.csdn.net/weixin_43236925/article/details/146382981 清晰易懂的 PHP 安裝與配置教程 12.6 查找每組行中含有最大或最小值的行 mysql> set max_price (select max(price) from painting); Query OK, 0 rows affected (0.01 sec)mysql> select artist.name…

ECS由淺入深第四節:ECS 與 Unity 傳統開發模式的結合?混合架構的藝術

ECS由淺入深第一節 ECS由淺入深第二節 ECS由淺入深第三節 ECS由淺入深第四節 ECS由淺入深第五節 盡管 ECS 帶來了顯著的性能和架構優勢&#xff0c;但在實際的 Unity 項目中&#xff0c;完全摒棄 GameObject 和 MonoBehaviour 往往是不現實的。Unity 引擎本身的大部分功能&…

Mac關閉觸控板

打開 “有鼠標或無線觸控板時忽略內建觸控板”選項即可 參考&#xff1a;Mac如何關閉觸控板防止誤觸&#xff1f;內置的設置就可以達成 - Mac天空

Python:Rich 終端富文本與界面樣式工具庫

??? 1、簡述 Rich 是一個強大的 Python 庫,用于在終端中呈現富文本和精美的格式,讓命令行界面(CLI)應用擁有現代、美觀的輸出效果。本文將深入介紹 Rich 的核心功能,并通過一系列實際示例展示其強大能力。 Rich 由 Will McGugan 開發,主要特點包括: 豐富的文本樣式:支…

深入解析享元模式:通過共享技術高效支持大量細粒度對象

深入解析享元模式&#xff1a;通過共享技術高效支持大量細粒度對象 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 總有一行代碼&#xff0c;能點亮萬千星辰。 &#x1f50d; 在技術的宇宙中&#xff0c;我愿做永不停歇的探索者。 ? 用代碼丈量世…

Docker高級管理

一、Docker 容器的網絡模式 當項目大規模使用 Docker 時&#xff0c;容器通信的問題也就產生了。要解決容器通信問題&#xff0c;必須先了解很多關于網絡的知識。Docker 的網絡模式非常豐富&#xff0c;可以滿足不同容器的通信要求&#xff0c;下表列出了這些網絡模式的主要信息…

ABP VNext + Tye:本地微服務編排與調試

ABP VNext Tye&#xff1a;本地微服務編排與調試 &#x1f680; &#x1f4da; 目錄ABP VNext Tye&#xff1a;本地微服務編排與調試 &#x1f680;TL;DR ?一、環境與依賴 &#x1f6e0;?二、核心配置詳解 &#x1f680;1. 主配置 tye.yaml三、多環境文件 &#x1f331;&am…

Vue響應式原理一:認識響應式邏輯

核心思想&#xff1a;當數據發生變化時&#xff0c;依賴該數據的代碼能夠自動重新執行Vue中的應用&#xff1a;在data或ref/reactive中定義的數據&#xff0c;當數據變化時template會自動更新template的本質&#xff1a; 是render()函數, 用變化之后的數據重新執行render()函數…

Redis:分組與設備在 Redis 中緩存存儲設計

一、緩存存儲結構設計 分組與設備的映射關系&#xff08;使用 Set 結構&#xff09;&#xff1a; 鍵格式&#xff1a;采用 group:{groupId}:devices 的格式作為 Redis 中 Set 的鍵&#xff0c;例如 group:1:devices 就代表了分組 ID 為 1 的分組所關聯的設備集合。值內容&#…