StampedLock入門教程

文章目錄

      • 一、理解“戳” (Stamp)
      • 二、為什么 `StampedLock` 能提高讀性能?秘密在于“樂觀讀”
      • StampedLock性能對比
        • 性能對比結果圖
      • 總結
  • StampedLock完整演示代碼
  • 對代碼的疑問之處
      • 問題一:為什么 `demonstrateOptimisticReadFailure` 中寫線程能修改成功?
      • 問題二:鎖升級這塊不是很理解,為什么結果是成功的?
        • 1. 什么是鎖升級?為什么需要它?
        • 2. 鎖升級什么時候會成功?什么時候會失敗?
        • 3. 為什么您的代碼里升級成功了?
        • 4. `finally` 塊中的 `lock.unlock(stamp)`
  • 🎯總結

直擊了 StampedLock 的設計核心。解釋它提升讀性能的秘密。


一、理解“戳” (Stamp)

首先,我們來理解“戳”(Stamp)是什么。

ReentrantReadWriteLock 中,lock()unlock() 是沒有參數和返回值的。你只要調用 lock() 獲取鎖,用完后調用 unlock() 釋放鎖即可。

StampedLock 完全不同。它的所有“上鎖”操作都會返回一個long類型的數字,這個數字就是所謂的**“戳” (Stamp)。而它所有的“解鎖”操作,都必須傳入這個“戳”**。

long stamp = lock.writeLock(); // 上寫鎖,返回一個戳
try {// ...
} finally {lock.unlockWrite(stamp); // 解鎖時必須傳入獲取鎖時得到的那個戳
}

為什么需要“戳”?
這個“戳”本質上是一個版本號或者狀態快照

  • 當沒有任何鎖時,它是一個初始值。
  • 當有線程獲取寫鎖時,這個“戳”的值會發生改變(比如增加一個版本號)。
  • 每次上鎖操作返回的“戳”都是獨一無二的。

所以,這句話 “在使用讀鎖、寫鎖時都必須配合【戳】使用”,指的就是這種 lock() 返回戳、unlock() 傳入戳的使用模式。這個“戳”是鎖狀態的憑證。

特別強調:獲取寫鎖的時候,版本號才會改變!!如果我們獲取讀鎖,是不會修改版本號的!!!!


二、為什么 StampedLock 能提高讀性能?秘密在于“樂觀讀”

ReentrantReadWriteLock 無論如何,都是一種悲觀鎖。即使是讀鎖,當多個讀線程和寫線程競爭時,仍然需要排隊、阻塞、上下文切換,這些都有性能開銷。它總是悲觀地認為“我讀的時候,很可能會有別人來寫”。

StampedLock 之所以性能更高,是因為它引入了一種全新的、ReentrantReadWriteLock 沒有的模式——樂觀讀 (Optimistic Reading)

樂觀讀的核心思想是:我非常樂觀地認為,在我讀取共享數據期間,根本不會有線程來修改它。

基于這個樂觀的假設,StampedLock 的樂觀讀操作如下:

  1. 嘗試樂觀讀 (tryOptimisticRead):

    • 線程想讀取共享數據,它先調用 lock.tryOptimisticRead()
    • 這個方法不會加任何鎖,不會阻塞線程,它只是瞬間獲取一下當前的“戳”(版本號),然后立即返回。這個過程幾乎沒有開銷,速度極快。
  2. 讀取共享數據:

    • 線程拿著這個“戳”,然后去讀取共享變量(比如 x, y 的值)。
  3. 驗證“戳” (validate):

    • 讀完數據后,線程必須調用 lock.validate(stamp),并傳入第一步獲取的那個“戳”。
    • validate 方法會檢查從第一步到當前時刻,有沒有寫操作發生過。它的判斷依據就是**“戳”的版本號有沒有變**。
      • 如果版本號沒變 (validate返回true):這說明在剛才的讀取期間,沒有任何寫操作來干擾。那么我們剛才讀取的數據就是一致的、有效的。這次“樂觀讀”成功了!
      • 如果版本號變了 (validate返回false):這說明在我們讀取數據的過程中,有一個“寫線程”插了進來,獲取了寫鎖,并修改了數據。那么我們剛才讀到的數據就是“臟”的、不可信的。
  4. 失敗后的補償:

    • 如果 validate 失敗了,說明樂觀失敗了,我們不能再這么樂觀。
    • 此時,程序必須“升級”為悲觀的讀鎖,即調用 lock.readLock() 來老老實實地加鎖,然后重新讀取一遍數據。

性能提升的關鍵點:
讀多寫少的場景下,絕大多數的樂觀讀操作都會成功。成功的樂觀讀,其開銷僅僅是兩次方法調用和一次版本號比較,完全沒有線程阻塞和上下文切換的開銷,甚至沒有CAS操作的開銷,性能幾乎和無鎖操作一樣快。

只有在極少數情況下(讀的過程中發生了寫),樂觀讀才會失敗,并升級為悲觀讀鎖,付出一點額外代價。但總體算下來,性能提升是巨大的。


StampedLock性能對比

package StampLock;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.StampedLock;public class StampedLockPerformanceDemo {// 共享數據static class SharedData {private int value = 0;// 三種不同的鎖private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();private final StampedLock stampedLock = new StampedLock();// 1. 使用 ReentrantReadWriteLock 讀取public int readWithRWLock() {rwLock.readLock().lock();try {return value;} finally {rwLock.readLock().unlock();}}// 2. 使用 StampedLock 的悲觀讀public int readWithStampedLock() {long stamp = stampedLock.readLock();try {return value;} finally {stampedLock.unlockRead(stamp);}}// 3. 使用 StampedLock 的樂觀讀(性能最好!)public int readWithOptimisticRead() {// 獲取樂觀讀戳long stamp = stampedLock.tryOptimisticRead();// 讀取數據(無鎖!)int currentValue = value;// 驗證期間是否有寫操作if (!stampedLock.validate(stamp)) {// 升級為悲觀讀stamp = stampedLock.readLock();try {currentValue = value;} finally {stampedLock.unlockRead(stamp);}}return currentValue;}// 寫操作(使用 StampedLock)public void write(int newValue) {long stamp = stampedLock.writeLock();try {value = newValue;} finally {stampedLock.unlockWrite(stamp);}}}public static void main(String[] args) throws InterruptedException {SharedData data = new SharedData();int threadCount = 100;int iterations = 100000;// 測試不同讀鎖的性能System.out.println("開始性能測試...\n");// 1. 測試 ReentrantReadWriteLocklong startTime = System.currentTimeMillis();testReadPerformance(data, threadCount, iterations, "RWLock");long rwLockTime = System.currentTimeMillis() - startTime;System.out.println("ReentrantReadWriteLock 耗時: " + rwLockTime + " ms");// 2. 測試 StampedLock 悲觀讀startTime = System.currentTimeMillis();testReadPerformance(data, threadCount, iterations, "StampedLock");long stampedLockTime = System.currentTimeMillis() - startTime;System.out.println("StampedLock 悲觀讀 耗時: " + stampedLockTime + " ms");// 3. 測試 StampedLock 樂觀讀startTime = System.currentTimeMillis();testReadPerformance(data, threadCount, iterations, "OptimisticRead");long optimisticTime = System.currentTimeMillis() - startTime;System.out.println("StampedLock 樂觀讀 耗時: " + optimisticTime + " ms");System.out.println("\n性能提升: " +String.format("%.2f", (double)rwLockTime / optimisticTime) + " 倍");}private static void testReadPerformance(SharedData data, int threadCount,int iterations, String lockType)throws InterruptedException {CountDownLatch latch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {new Thread(() -> {for (int j = 0; j < iterations; j++) {switch (lockType) {case "RWLock":data.readWithRWLock();break;case "StampedLock":data.readWithStampedLock();break;case "OptimisticRead":data.readWithOptimisticRead();break;}}latch.countDown();}).start();}latch.await();}
}

無鎖的樂觀讀對性能提升極其明顯!提升足足150倍!!!但是呢,StampedLock也不是萬能的,他只是首先嘗試使用樂觀讀(無鎖),如果發現版本號不對勁,中間被修改過了,就需要加鎖,重新讀取,丟掉臟數據!!
關鍵代碼:

// 3. 使用 StampedLock 的樂觀讀(性能最好!)public int readWithOptimisticRead() {// 獲取樂觀讀戳long stamp = stampedLock.tryOptimisticRead();// 讀取數據(無鎖!)int currentValue = value;// 驗證期間是否有寫操作if (!stampedLock.validate(stamp)) {// 升級為悲觀讀stamp = stampedLock.readLock();try {currentValue = value;} finally {stampedLock.unlockRead(stamp);}}return currentValue;}
性能對比結果圖

在這里插入圖片描述

總結

  • “配合【戳】使用”:指的是StampedLock所有上鎖/解鎖操作都圍繞一個long類型的版本號(戳)來進行。
  • 提高讀性能的原因StampedLock引入了樂觀讀機制。在“讀多寫少”的場景下,樂觀讀允許線程在不加鎖的情況下讀取數據,并通過“戳”來驗證數據的一致性。這個過程避免了絕大多數讀操作的加鎖、阻塞和線程切換開銷,從而極大地提升了讀取性能。它是用一種“先上車后補票”的樂觀策略換來了性能的飛躍。

StampedLock完整演示代碼

package StampLock;import java.util.concurrent.locks.StampedLock;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;
import java.util.Random;
import java.util.concurrent.locks.StampedLock;
public class SimpleStampedLockDemo {private static class Point {private double x, y;private final StampedLock lock = new StampedLock();// 移動點的位置(寫操作)public void move(double deltaX, double deltaY) {long stamp = lock.writeLock();try {x += deltaX;y += deltaY;System.out.println(Thread.currentThread().getName() +" 移動點到: (" + x + ", " + y + ")");} finally {lock.unlockWrite(stamp);}}// 計算到原點的距離(樂觀讀)- 修正版public double distanceFromOrigin() {// 1. 嘗試樂觀讀long stamp = lock.tryOptimisticRead();// 2. 讀取數據double currentX = x;double currentY = y;// 模擬讀取過程需要一些時間(讓寫線程有機會介入)try {Thread.sleep(100);  // 模擬復雜計算} catch (InterruptedException e) {e.printStackTrace();}// 3. 驗證在讀取過程中數據是否被修改if (!lock.validate(stamp)) {// 數據被修改了,需要加鎖重新讀取System.out.println(Thread.currentThread().getName() +" 樂觀讀失敗,升級為悲觀讀");stamp = lock.readLock();try {currentX = x;currentY = y;} finally {lock.unlockRead(stamp);}} else {System.out.println(Thread.currentThread().getName() +" 樂觀讀成功!");}return Math.sqrt(currentX * currentX + currentY * currentY);}// 專門用于演示樂觀讀失敗的方法public void demonstrateOptimisticReadFailure() {System.out.println("開始演示樂觀讀失敗場景...");// 讀線程Thread reader = new Thread(() -> {long stamp = lock.tryOptimisticRead();System.out.println(Thread.currentThread().getName() +" 獲取樂觀讀戳: " + stamp);// 讀取第一個值double currentX = x;System.out.println(Thread.currentThread().getName() +" 讀取 x = " + currentX);// 故意等待,讓寫線程有機會修改數據try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}// 讀取第二個值double currentY = y;System.out.println(Thread.currentThread().getName() +" 讀取 y = " + currentY);// 驗證if (!lock.validate(stamp)) {System.out.println(Thread.currentThread().getName() +" ? 樂觀讀失敗!數據在讀取過程中被修改了");// 重新用悲觀讀stamp = lock.readLock();try {currentX = x;currentY = y;System.out.println(Thread.currentThread().getName() +" 使用悲觀讀重新讀取: (" + currentX + ", " + currentY + ")");} finally {lock.unlockRead(stamp);}} else {System.out.println(Thread.currentThread().getName() +" ? 樂觀讀成功");}}, "讀線程");// 寫線程Thread writer = new Thread(() -> {try {// 等待讀線程開始Thread.sleep(100);System.out.println(Thread.currentThread().getName() +" 準備修改數據...");move(10, 10);} catch (InterruptedException e) {e.printStackTrace();}}, "寫線程");try {reader.start();writer.start();reader.join();writer.join();} catch (InterruptedException e) {e.printStackTrace();}}// 鎖升級示例public void moveToOriginIfInFirstQuadrant() {long stamp = lock.readLock();try {if (x > 0 && y > 0) {// 嘗試將讀鎖升級為寫鎖long writeStamp = lock.tryConvertToWriteLock(stamp);if (writeStamp != 0) {stamp = writeStamp;System.out.println(Thread.currentThread().getName() +" ? 成功將讀鎖升級為寫鎖");x = 0;y = 0;} else {System.out.println(Thread.currentThread().getName() +" ? 讀鎖升級失敗,重新獲取寫鎖");lock.unlockRead(stamp);stamp = lock.writeLock();try {x = 0;y = 0;} finally {lock.unlockWrite(stamp);return;}}}} finally {lock.unlock(stamp);}}}public static void main(String[] args) throws InterruptedException {Point point = new Point();System.out.println("=== StampedLock 簡單示例 ===\n");// 示例1:基本的讀寫操作System.out.println("1. 基本讀寫操作:");point.move(3, 4);System.out.println("距離原點: " + point.distanceFromOrigin());System.out.println();// 示例2:單線程樂觀讀(肯定成功)System.out.println("2. 單線程樂觀讀(肯定成功):");double distance = point.distanceFromOrigin();System.out.println("距離: " + distance);System.out.println();// 示例3:演示樂觀讀失敗System.out.println("3. 并發場景下的樂觀讀失敗:");point.demonstrateOptimisticReadFailure();System.out.println();// 示例4:演示鎖升級System.out.println("4. 鎖升級示例:");point.move(5, 5);  // 確保在第一象限Thread upgrader = new Thread(() -> {point.moveToOriginIfInFirstQuadrant();}, "升級線程");upgrader.start();upgrader.join();System.out.println("\n最終位置: (" + point.x + ", " + point.y + ")");}
}

在這里插入圖片描述

對代碼的疑問之處

當時我對控制臺打印的疑問是:
sleep不釋放鎖,為什么reader在sleep了200ms之后,雖然Writer還有100ms的時間可以獲取鎖,但是reader不釋放鎖啊,為什么Writer還是能獲取到鎖??
其實我是理解錯誤了!在樂觀讀模式下,讀線程在調用sleep時,根本沒有持有任何鎖!

我們來逐一詳細拆解。


問題一:為什么 demonstrateOptimisticReadFailure 中寫線程能修改成功?

您對線程執行順序的理解出現了一點偏差,這也是并發編程初學者最容易遇到的一個困惑點。您可能是這樣想的:

  • 您的設想(串行思路):讀線程啟動 -> 讀x -> 睡200ms -> 讀y -> 結束。然后寫線程啟動 -> 睡100ms -> 修改。

但實際情況是,t.start() 只是告訴操作系統“這個線程可以開始運行了”,但具體什么時候運行、運行多長時間,都由 CPU的線程調度器來決定。兩個線程一旦 start(),就可以看作是在同時、并行地執行。

我們來梳理一下實際的事件時間線

  1. T=0ms: main線程調用了 reader.start()writer.start()。此時,讀線程和寫線程都進入了“就緒”狀態,隨時可以被CPU執行。

  2. T=~1ms (舉例): 讀線程搶到了CPU時間片。

    • 它執行 lock.tryOptimisticRead(),獲取了版本號(比如512)。
    • 它讀取了 x 的值(3.0)。
    • 然后它調用 Thread.sleep(200)主動放棄CPU,進入了休眠狀態。它要等200毫秒后才能醒來。
  3. T=~2ms: 寫線程搶到了CPU時間片。

    • 它調用 Thread.sleep(100),也主動放棄CPU,進入休眠。它只需要等100毫秒。
  4. T=~102ms: 寫線程的100ms睡眠時間結束了!

    • 它被喚醒,重新進入“就緒”狀態,并很快搶到CPU。
    • 它調用 move(10, 10),成功獲取了寫鎖(因為此時沒有其他鎖),將 xy 修改為 (13.0, 14.0)。
    • 寫線程的工作完成了。
  5. T=~201ms: 讀線程的200ms睡眠時間現在才結束!

    • 它被喚醒,從 sleep(200) 的下一行代碼繼續執行。
    • 它開始讀取 y 的值。但此時的 y 已經是被寫線程修改后的 14.0
    • 它讀取完畢后,調用 lock.validate(512)
    • StampedLock 發現,從它獲取版本號512到現在,中間發生了一次寫操作(版本號已經變了)。
    • 因此 validate 返回 false,樂觀讀失敗。

結論:代碼完美地達到了演示失敗的目的。正是因為寫線程的睡眠時間(100ms)比讀線程的睡眠時間(200ms)短,所以寫操作總能發生在讀操作的“讀取x”和“讀取y”這兩個動作之間,從而導致樂觀讀驗證失敗。


問題二:鎖升級這塊不是很理解,為什么結果是成功的?

我們來深入理解一下 tryConvertToWriteLock 這個“鎖升級”操作。

1. 什么是鎖升級?為什么需要它?

想象一個場景:你需要先讀取一個共享數據,根據數據的值,再決定是否要修改它。

  • 常規的笨辦法

    1. 先加讀鎖
    2. 讀取數據,發現需要修改。
    3. 釋放讀鎖
    4. 再加寫鎖
    5. (問題來了) 在你釋放讀鎖和獲取寫鎖的這個“空檔期”,很可能有另一個線程沖進來修改了數據,那你剛才的判斷就白費了,你必須重新讀取和判斷,非常麻煩。
  • 鎖升級的聰明辦法
    tryConvertToWriteLock 提供了一個在持有讀鎖的情況下,直接嘗試轉變為寫鎖的機會,中間不釋放任何鎖,從而避免了上述的“空檔期”問題。這是一種優化。

2. 鎖升級什么時候會成功?什么時候會失敗?

tryConvertToWriteLock 是一個“樂觀”的嘗試,它成功的條件非常苛刻

  • 成功條件:當嘗試升級時,當前線程必須是唯一的讀者。也就是說,不能有任何其他線程持有讀鎖。如果StampedLock發現只有你這一個讀者,它就會很順利地把你的讀鎖“升級”成寫鎖,并返回一個新的、代表寫鎖的“戳”。
  • 失敗條件:只要當時還有任何一個其他線程也持有讀鎖,升級就會立即失敗,并返回 0。這是為了防止死鎖(如果兩個讀線程都想升級成寫鎖,它們會相互等待對方釋放讀鎖,從而死鎖)。
3. 為什么您的代碼里升級成功了?

我們看一下您的 main 函數中調用這部分的代碼:

// 示例4:演示鎖升級
System.out.println("4. 鎖升級示例:");
point.move(5, 5);  // 確保在第一象限
Thread upgrader = new Thread(() -> {point.moveToOriginIfInFirstQuadrant();
}, "升級線程");
upgrader.start();
upgrader.join();

在這里,您只創建了一個名為“升級線程”的線程去執行 moveToOriginIfInFirstQuadrant 這個方法。

所以,當這個線程執行到 lock.tryConvertToWriteLock(stamp) 時,它自己是當前唯一的讀者,沒有任何其他線程持有讀鎖。因此,它完全滿足了升級成功的苛刻條件,所以升級必然成功,并打印出 ? 成功將讀鎖升級為寫鎖

4. finally 塊中的 lock.unlock(stamp)

您可能會注意到,finally 塊里只有一個 lock.unlock(stamp),它是如何知道該解鎖讀鎖還是寫鎖的呢?

這也是StampedLock的一個巧妙之處。unlock(stamp) 方法會根據傳入的“戳”的類型,來自動判斷是該執行 unlockRead 還是 unlockWrite

  • 如果升級失敗,stamp 變量里保存的還是最初的讀鎖戳unlock(stamp) 就執行讀鎖釋放。
  • 如果升級成功,代碼 stamp = writeStamp; 會把寫鎖戳賦給 stamp 變量,unlock(stamp) 就執行寫鎖釋放。

這種設計簡化了 finally 塊的邏輯。

🎯總結

  1. 為什么叫"戳"(Stamp)?

    • 每次獲取鎖都會返回一個唯一的數字(戳)
    • 釋放鎖時必須提供對應的戳
    • 就像票據系統,確保鎖的正確配對
  2. 為什么能提高讀性能?

    • 樂觀讀:不加鎖,直接讀取,性能最高
    • 只在數據被修改時才升級為真正的鎖
    • 適合讀多寫少的場景
  3. 使用場景

    • 讀操作遠多于寫操作
    • 對讀性能要求很高
    • 可以容忍偶爾的讀重試
  4. 注意事項

    • 不支持重入
    • 必須正確管理戳
    • 不支持條件變量(Condition)

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

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

相關文章

基于云計算的振動弦分析:諧波可視化與波動方程參數理解-AI云計算數值分析和代碼驗證

振動弦方程是一個基礎的偏微分方程&#xff0c;它描述了彈性弦的橫向振動。其應用范圍廣泛&#xff0c;不僅可用于模擬樂器和一般的波動現象&#xff0c;更是數學物理以及深奧的弦理論中的重要基石。 ??AI云計算數值分析和代碼驗證 振動弦方程是描述固定兩端彈性弦橫向振動的…

Qt .pro配置gcc相關命令(三):-W1、-L、-rpath和-rpath-link

目錄 1.Linux 動態庫相關知識 1.1.動態庫查找路徑 1.2.查看程序依賴的動態庫 1.3.修改動態庫查找路徑的方法 1.4.動態鏈接器緩存管理 2.-Wl參數 3.-L選項&#xff08;編譯時路徑&#xff09; 4.-rpath參數(運行時路徑) 5.-rpath-link 參數 6.常見問題與解決方案 7.總…

Hoppscotch

官方地址 xixiaxiazxiaxix下載 ? Hoppscotch Hoppscotch 是一款輕量級、基于 Web 的 API 開發套件&#xff0c;其核心功能和特點如下&#xff1a; 核心功能3 交互式 API 測試&#xff1a;允許用戶實時發送請求并查看響應&#xff0c;方便記錄 API 行為&#xff0c;在記錄響…

RabbitMQ 知識詳解(Java版)

RabbitMQ 知識詳解&#xff08;Java版&#xff09; RabbitMQ 是一個開源的消息代理&#xff0c;實現了高級消息隊列協議&#xff08;AMQP&#xff09;。它用于在分布式系統中實現應用解耦、異步通信和流量削峰。 核心概念 生產者(Producer)&#xff1a;發送消息的應用消費者(…

Flink task、Operator 和 UDF 之間的關系

要真正駕馭 Flink 并構建出高效、穩定、可擴展的流處理應用&#xff0c;僅僅停留在 API 的表面使用是遠遠不夠的。深入理解其內部的運行機制&#xff0c;洞悉數據從代碼到分布式執行的完整生命周期&#xff0c;以及明晰各個核心組件之間錯綜復雜而又協同工作的關系&#xff0c;…

Veeam Backup Replication系統的安裝與使用

Veeam Backup & Replication系統安裝與使用 系統簡介 核心功能 備份與恢復&#xff1a;專注于虛擬化環境&#xff08;如VMware和Hyper-V&#xff09;的備份與恢復&#xff0c;支持物理服務器和云環境。快速恢復&#xff1a;提供即時恢復功能&#xff0c;可在幾分鐘內恢復…

十四、【ESP32全棧開發指南:搭建輕量級HTTP服務器】

一、HTTP協議基礎 HTTP&#xff08;Hyper Text Transfer Protocol&#xff09;作為互聯網基礎協議&#xff0c;采用請求-響應模型工作&#xff1a; 1.1 HTTP請求組成 GET /uri?query1value1 HTTP/1.1 // 請求行&#xff08;方法URI協議版本&#xff09; Host: example…

java中LinkedList和ArrayList的區別和聯系?

我們被要求解釋Java中LinkedList和ArrayList的區別和聯系。下面將分別從實現原理、性能特點、使用場景等方面進行詳細說明&#xff0c;并總結它們的相同點和不同點。 # 一、聯系&#xff08;共同點&#xff09; 1. 都實現了List接口&#xff0c;因此具有List接口的所有方法&…

明遠智睿SD2351核心板:邊緣計算時代的工業級核心引擎深度解析

在工業4.0與物聯網深度融合的背景下&#xff0c;邊緣計算設備正從單一功能模塊向高集成度、智能化平臺演進。明遠智睿推出的SD2351核心板&#xff0c;憑借其異構計算架構、工業級接口資源和全棧技術生態&#xff0c;重新定義了邊緣計算設備的性能邊界。本文將從技術架構、場景適…

Flask 動態模塊注冊

目錄 1. 項目概述2. 項目結構3. 核心組件解析3.1 動態模塊注冊系統 (api/__init__.py)3.2 應用程序入口 (setup_demo.py) 4. 模塊開發指南4.1 標準模塊 (*_app.py)4.2 SDK模塊 (sdk/*.py) 5. URL路徑規則6. 如何使用6.1 啟動應用6.2 添加新模塊 7. 工作原理 1. 項目概述 這個項…

JVM 內存、JMM內存與集群機器節點內存的聯系

目錄 1、JVM 內存 1.1、分配機制 1.2、jvm模型位置 1.3、字節碼內存塊 2、JMM內存 2.1、JMM模型 2.2、工作流程圖 1、工作內存與主內存的交互 2. 多線程下的主內存與堆內存交互 2.3、 主內存與工作內存的同步方案 1、volatile 2、synchronized 3、final 3、內存使…

學習昇騰開發的第一天--環境配置

1、昇騰社區官網&#xff1a;昇騰社區官網-昇騰萬里 讓智能無所不及 2、產品-->選擇開發者套件-->點擊制卡工具的下載&#xff1a;資源-Atlas 200I DK A2-昇騰社區 3、如果制卡工具不能使用在線制卡&#xff0c;可以下載鏡像到本地使用本地制卡&#xff1a;Linux系統制…

Android WebView 深色模式適配方案總結

Android WebView 深色模式適配方案總結 在 Android WebView 中適配深色模式&#xff08;Dark Mode&#xff09;是一個常見的需求&#xff0c;尤其是當加載的網頁沒有原生支持 prefers-color-scheme 時。本文將介紹 3 種主流方案&#xff0c;并分析它們的優缺點&#xff0c;幫助…

項目練習:使用mybatis的foreach標簽,實現union all的拼接語句

文章目錄 一、需求說明二、需求分析三、代碼實現四、報表效果 一、需求說明 在sql查詢數據后&#xff0c;對數據分組統計。并最后進行總計。 二、需求分析 最終&#xff0c;我想用sql來實現這個統計和查詢的功能。 那么&#xff0c;怎么又查詢&#xff0c;又統計了&#xf…

7.7 Extracting and saving responses

Chapter 7-Fine-tuning to follow instructions 7.7 Extracting and saving responses 在本節中&#xff0c;我們保存測試集響應以便在下一節中評分&#xff0c;除此之外保存模型的副本以供將來使用。 ? 首先&#xff0c;讓我們簡單看看finetuned模型生成的響應 torch.manu…

計算機網絡第3章(上):數據鏈路層全解析——組幀、差錯控制與信道效率

目錄 一、數據鏈路層的功能二、組幀2.1 字符計數法&#xff08;Character Count&#xff09;2.2 字符填充法&#xff08;Character Stuffing&#xff09;2.3 零比特填充法2.4 違規編碼法 三、差錯控制3.1 檢錯編碼&#xff08;奇偶校驗碼&#xff09;3.2 循環冗余校驗&#xff…

鑄鐵試驗平臺的重要性及應用前景

鑄鐵作為一種重要的金屬材料&#xff0c;在工業生產中扮演著舉足輕重的角色。為了確保鑄鐵制品的質量和性能&#xff0c;鑄鐵材料的試驗是必不可少的環節。而鑄鐵試驗平臺則是進行鑄鐵試驗的關鍵設備之一&#xff0c;它為鑄鐵材料的研究和開發提供了重要的技術支持。本文將探討…

std::shared_ptr引起內存泄漏的例子

目錄 一、循環引用&#xff08;最常見場景&#xff09; 示例代碼 內存泄漏原因 二、共享指針管理的對象包含自身的 shared_ptr 示例代碼 內存泄漏&#xff08;或雙重釋放&#xff09;原因 三、解決方案 1. 循環引用&#xff1a;使用 std::weak_ptr 2. 對象獲取自身的 …

AI 知識數據庫搭建方案:從需求分析到落地實施

AI 知識數據庫的搭建需結合業務場景、數據特性與技術架構&#xff0c;形成系統化解決方案。以下是一套完整的搭建框架&#xff0c;涵蓋規劃、設計、實施及優化全流程&#xff1a; 一、前期規劃&#xff1a;需求分析與目標定義 1. 明確業務場景與知識需求 場景導向&#xff1a…

Tensorflow 基礎知識:變量、常量、占位符、Session 詳解

在深度學習領域,TensorFlow 是一個廣泛使用的開源機器學習框架。想要熟練使用 TensorFlow 進行模型開發,掌握變量、常量、占位符和 Session 這些基礎知識是必不可少的。接下來,我們就深入了解一下它們的概念、用處,并通過代碼示例進行演示。 一、常量(Constant) 常量,顧…