深入學習并發編程中的volatile

volatile 的作用

  • 保證變量的內存可見性
  • 禁止指令重排序

1.保證此變量對所有的線程的可見性,當一個線程修改了這個變量的值,volatile 保證了新值能立即同步到主內存,其它線程每次使用前立即從主內存刷新。 但普通變量做不到這點,普通變量的值在線程間傳遞均需要通過主內存來完成。
2.禁止指令重排序優化。有volatile修飾的變量,賦值后多執行了一個“load addl $0x0, (%esp)”操作,這個操作相當于一個內存屏障(指令重排序時不能把后面的指令重排序到內存屏障之前的位置)。


可見性

在理解 volatile 的內存可見性前,我們先來看看這個比較常見的多線程訪問共享變量的例子。

/*** 變量的內存可見性例子*/
public class VolatileExample {/*** main 方法作為一個主線程*/public static void main(String[] args) {MyThread myThread = new MyThread();// 開啟線程myThread.start();// 主線程執行for (; ; ) {if (myThread.isFlag()) {System.out.println("主線程訪問到 flag 變量");}}}}/*** 子線程類*/
class MyThread extends Thread {private boolean flag = false;@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 修改變量值flag = true;System.out.println("flag = " + flag);}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}
}

執行上面的程序,你會發現,控制臺永遠都不會輸出 “主線程訪問到 flag 變量” 這句話。我們可以看到,子線程執行時已經將 flag 設置成 true,但主線程執行時沒有讀到 flag 的最新值,導致控制臺沒有輸出上面的句子。

那么,我們思考一下為什么會出現這種情況呢?這里我們就要了解一下 Java 內存模型(簡稱 JMM)。

Java 內存模型

JMM(Java Memory Model):Java 內存模型,是 Java 虛擬機規范中所定義的一種內存模型,Java 內存模型是標準化的,屏蔽掉了底層不同計算機的區別。也就是說,JMM 是 JVM 中定義的一種并發編程的底層模型機制。

JMM 定義了線程和主內存之間的抽象關系:線程之間的共享變量存儲在主內存中,每個線程都有一個私有的本地內存,本地內存中存儲了該線程以讀/寫共享變量的副本。

JMM 的規定:
- 所有的共享變量都存儲于主內存。這里所說的變量指的是實例變量和類變量,不包含局部變量,因為局部變量是線程私有的,因此不存在競爭問題。

  • 每一個線程還存在自己的工作內存,線程的工作內存,保留了被線程使用的變量的工作副本。
  • 線程對變量的所有的操作(讀,取)都必須在工作內存中完成,而不能直接讀寫主內存中的變量。
  • 不同線程之間也不能直接訪問對方工作內存中的變量,線程間變量的值的傳遞需要通過主內存中轉來完成。

JMM 的抽象示意圖:

在這里插入圖片描述

然而,JMM 這樣的規定可能會導致線程對共享變量的修改沒有即時更新到主內存,或者線程沒能夠即時將共享變量的最新值同步到工作內存中,從而使得線程在使用共享變量的值時,該值并不是最新的。

正因為 JMM 這樣的機制,就出現了可見性問題。也就是我們上面那個例子出現的問題

那我們要如何解決可見性問題呢?接下來我們就聊聊內存可見性以及可見性問題的解決方案。

內存可見性

內存可見性是指當一個線程修改了某個變量的值,其它線程總是能知道這個變量變化。也就是說,如果線程 A 修改了共享變量 V 的值,那么線程 B 在使用 V 的值時,能立即讀到 V 的最新值。

可見性問題的解決方案

我們如何保證多線程下共享變量的可見性呢?也就是當一個線程修改了某個值后,對其他線程是可見的。

這里有兩種方案:加鎖使用 volatile 關鍵字

下面我們使用這兩個方案對上面的例子進行改造。

加鎖

使用 synchronizer 進行加鎖。

/*** main 方法作為一個主線程*/public static void main(String[] args) {MyThread myThread = new MyThread();// 開啟線程myThread.start();// 主線程執行for (; ; ) {synchronized (myThread) {if (myThread.isFlag()) {System.out.println("主線程訪問到 flag 變量");}}}}

這里大家應該有個疑問是,為什么加鎖后就保證了變量的內存可見性了? 因為當一個線程進入 synchronizer 代碼塊后,線程獲取到鎖,會清空本地內存,然后從主內存中拷貝共享變量的最新值到本地內存作為副本,執行代碼,又將修改后的副本值刷新到主內存中,最后線程釋放鎖。

這里除了 synchronizer 外,其它鎖也能保證變量的內存可見性。

使用 volatile 關鍵字

使用 volatile 關鍵字修飾共享變量。

/*** 子線程類*/
class MyThread extends Thread {private volatile boolean flag = false;@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 修改變量值flag = true;System.out.println("flag = " + flag);}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}
}

使用 volatile 修飾共享變量后,每個線程要操作變量時會從主內存中將變量拷貝到本地內存作為副本,當線程操作變量副本并寫回主內存后,會通過 CPU 總線嗅探機制 告知其他線程該變量副本已經失效,需要重新從主內存中讀取。

volatile 保證了不同線程對共享變量操作的可見性,也就是說一個線程修改了 volatile 修飾的變量,當修改后的變量寫回主內存時,其他線程能立即看到最新值。

接下來我們就聊聊一個比較底層的知識點:總線嗅探機制

總線嗅探機制

在現代計算機中,CPU 的速度是極高的,如果 CPU 需要存取數據時都直接與內存打交道,在存取過程中,CPU 將一直空閑,這是一種極大的浪費,所以,為了提高處理速度,CPU 不直接和內存進行通信,而是在 CPU 與內存之間加入很多寄存器,多級緩存,它們比內存的存取速度高得多,這樣就解決了 CPU 運算速度和內存讀取速度不一致問題。

由于 CPU 與內存之間加入了緩存,在進行數據操作時,先將數據從內存拷貝到緩存中,CPU 直接操作的是緩存中的數據。但在多處理器下,將可能導致各自的緩存數據不一致(這也是可見性問題的由來),為了保證各個處理器的緩存是一致的,就會實現緩存一致性協議,而嗅探是實現緩存一致性的常見機制

在這里插入圖片描述

注意,緩存的一致性問題,不是多處理器導致,而是多緩存導致的。

嗅探機制工作原理:每個處理器通過監聽在總線上傳播的數據來檢查自己的緩存值是不是過期了,如果處理器發現自己緩存行對應的內存地址修改,就會將當前處理器的緩存行設置無效狀態,當處理器對這個數據進行修改操作的時候,會重新從主內存中把數據讀到處理器緩存中。

注意:基于 CPU 緩存一致性協議,JVM 實現了 volatile 的可見性,但由于總線嗅探機制,會不斷的監聽總線,如果大量使用 volatile 會引起總線風暴。所以,volatile 的使用要適合具體場景。

可見性問題小結

上面的例子中,我們看到,使用 volatile 和 synchronized 鎖都可以保證共享變量的可見性。相比 synchronized 而言,volatile 可以看作是一個輕量級鎖,所以使用 volatile 的成本更低,因為它不會引起線程上下文的切換和調度。但 volatile 無法像 synchronized 一樣保證操作的原子性。

下面我們來聊聊 volatile 的原子性問題。


原子性

所謂的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了執行并且不會受到任何因素的干擾而中斷,要么所有的操作都不執行。

在多線程環境下,volatile 關鍵字可以保證共享數據的可見性,但是并不能保證對數據操作的原子性。也就是說,多線程環境下,使用 volatile 修飾的變量是線程不安全的

要解決這個問題,我們可以使用鎖機制,或者使用原子類(如 AtomicInteger)。

這里特別說一下,對任意單個使用 volatile 修飾的變量的讀 / 寫是具有原子性,但類似于 flag = !flag 這種復合操作不具有原子性。簡單地說就是,單純的賦值操作是原子性的


禁止指令重排序

什么是重排序?

有序性:即程序執行的順序按照代碼的先后順序執行。

一般來說,處理器為了提高程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行先后順序同代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。

從 Java 源代碼到最終執行的指令序列,會分別經歷下面三種重排序:

在這里插入圖片描述

雖然處理器會對指令進行重排序,但是它會保證程序最終結果會和代碼順序執行結果相同,那么它靠什么保證的呢?靠的是數據依賴性:

編譯器和處理器在重排序時,會遵守數據依賴性,編譯器和處理器不會改變存在數據依賴關系的兩個操作的執行順序。

舉例如下代碼

double pi  = 3.14;    //A  
double r   = 1.0;     //B  
double area = pi * r * r; //C  

上面三個操作的數據依賴關系如下圖所示

在這里插入圖片描述

A和C之間存在數據依賴關系,同時B和C之間也存在數據依賴關系。因此在最終執行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,程序的結果將會被改變)。但A和B之間沒有數據依賴關系,編譯器和處理器可以重排序A和B之間的執行順序。下圖是該程序的兩種執行順序:

在這里插入圖片描述

在計算機中,軟件技術和硬件技術有一個共同的目標:在不改變程序執行結果的前提下,盡可能的開發并行度。編譯器和處理器都遵從這一目標。
這里所說的數據依賴性僅針對
單個處理器中執行的指令序列和單個線程中執行的操作
,在單線程程序中,對存在控制依賴的操作重排序,不會改變執行結果;但在多線程程序中,對存在控制依賴的操作重排序,可能會改變程序的執行結果。這是就需要內存屏障來保證可見性了。

為了更好地理解重排序,請看下面的部分示例代碼:

int a = 0;
flag = false;// 線程 A
a = 1;           // 1
flag = true;     // 2// 線程 B
if (flag) { // 3int i = a; // 4
}

單看上面的程序好像沒有問題,最后 i 的值是 1。但是為了提高性能,編譯器和處理器常常會在不改變數據依賴的情況下對指令做重排序(線程A中的a = 1; flag = true沒有依賴關系)。假設線程 A 在執行時被重排序成先執行代碼 2,再執行代碼 1;而線程 B 在線程 A 執行完代碼 2 后,讀取了 flag 變量。由于條件判斷為真,線程 B 將讀取變量 a。此時,變量 a 還根本沒有被線程 A 寫入,那么 i 最后的值是 0,導致執行結果不正確。那么如何程序執行結果正確呢?這里仍然可以使用 volatile 關鍵字。

這個例子中, 使用 volatile 不僅保證了變量的內存可見性,還禁止了指令的重排序,即保證了 volatile 修飾的變量編譯后的順序與程序的執行順序一樣。那么使用 volatile 修飾 flag 變量后,在線程 A 中,保證了代碼 1 的執行順序一定在代碼 2 之前。

那么,讓我們繼續往下探索, volatile 是如何禁止指令重排序的呢?這里我們將引出一個概念:內存屏障指令

內存屏障指令

java編譯器會在生成指令系列時在適當的位置會插入內存屏障指令來禁止特定類型的處理器重排序。

內存屏障有兩個作用:

1.阻止屏障兩側的指令重排序;
2.強制寫入緩存中的最新數據更新寫入主內存,讓其他線程可見;讓高速緩存中的數據失效,強制從新從主內存加載數據。

內存屏障分為兩種:Store Barrier 和 Load Barrier 即 寫屏障和讀屏障。

  • 寫屏障(Store Barrier)

    • 阻止屏障兩側的指令重排序。
    • 清空CPU的Store Buffer,將修改刷入主存,并觸發緩存一致性協議(如MESI)廣播Invalid信號,使其他核心的緩存行失效。

    讀屏障(Load Barrier)

    • 阻止屏障兩側的指令重排序。
    • 強制CPU處理Invalidate Queue中的失效請求,若緩存行狀態為Invalid,則從主存重新加載數據。

補充:

Store Buffer(存儲緩沖區):位于 CPU 核心與 L1 緩存之間,優化寫操作性能。

Invalidate Queue(失效隊列):位于 L1 緩存與總線嗅探單元之間,加速對失效請求的響應。

java的內存屏障通常所謂的四種即StoreStore,StoreLoad,LoadLoad,LoadStore,實際上也是上述兩種的組合,完成一系列的屏障和數據同步功能。

屏障類型核心作用防止的重排序
StoreStore保證普通寫優先完成寫-寫重排序
StoreLoad確保寫操作全局可見(全能屏障)寫-讀重排序
LoadLoad防止讀操作重排序讀-讀重排序
LoadStore防止讀-寫重排序讀-寫重排序

下面我們來看看 volatile 讀 / 寫時是如何插入內存屏障的,見下圖:

**在這里插入圖片描述
**

從上圖,我們可以知道 volatile 讀 / 寫插入內存屏障規則:

  • 在每個 volatile 寫操作的前后分別插入一個 StoreStore 屏障和一個 StoreLoad 屏障。

    作用:禁止普通寫與volatile寫重排序;強制刷新寫緩沖區,觸發MESI廣播失效信號。

  • 在每個 volatile 讀操作的后面插入 LoadLoad 屏障和 LoadStore 屏障。

    作用:處理失效隊列,強制加載最新值;禁止后續普通寫與volatile讀重排序。


緩存一致性協議&內存屏障

原理總結

  • 可見性通過內存屏障(強制刷緩存/失效緩存)+ MESI 協議(同步緩存狀態)實現
  • 有序性通過內存屏障(禁止重排序)約束指令執行順序

二者共同確保 volatile 變量的“可見性”與“有序性”,但不保證原子性(如 i++ 需配合鎖或原子類)。

1. 緩存一致性協議(MESI)作用
  • 數據一致性:通過總線嗅探和狀態機(Modified/Exclusive/Shared/Invalid)保證多核緩存數據一致。
  • 失效觸發:
    • 核心A修改共享數據時,通過總線廣播Invalid信號,使其他核心的緩存行失效(狀態置為Invalid
    • 其他核心讀取失效數據時,強制從主存重新加載最新值

2. 內存屏障作用
(1) 禁止重排序:確保操作有序性

指令重排序是編譯器和CPU為優化性能對指令執行順序的調整,單線程安全但多線程會導致邏輯錯誤。通過插入屏障指令,防止編譯器和CPU對指令亂序執行。

(2) 強制同步內存:確保可見性與一致性

內存屏障通過強制刷新緩存狀態,解決多核CPU因私有緩存(Store Buffer/Invalidate Queue)導致的數據不一致問題:

刷新寫緩沖區(強制寫操作全局可見)

  • 機制:
    • 寫屏障(如StoreStore/StoreLoad)清空CPU的Store Buffer,將修改刷入主存,并觸發緩存一致性協議(如MESI)廣播Invalid信號,使其他核心的緩存行失效。
    • 示例volatile寫后插入StoreLoad屏障,強制寫緩沖區數據刷入主存,確保其他線程立即可見。

處理失效隊列(強制讀操作加載最新值)

  • 機制:
    • 讀屏障(如LoadLoad/LoadStore)強制CPU處理Invalidate Queue中的失效請求,若緩存行狀態為Invalid,則從主存重新加載數據。
    • 示例volatile讀前插入LoadLoad屏障,確保讀取前處理完所有失效請求,避免讀到舊緩存。
(3) 與緩存一致性協議(MESI)的協同
  • MESI協議角色
    通過緩存行狀態(Modified/Exclusive/Shared/Invalid)管理多核數據一致性,但??無法主動保證順序性??。
  • 屏障與協議聯動:
    • 寫屏障觸發MESI廣播Invalid信號,使其他核心緩存失效;
    • 讀屏障確保失效請求被處理,依賴MESI狀態(如Invalid)重新加載數據。

happens-before

什么是happens-before?

一方面,程序員需要JMM提供一個強的內存模型來編寫代碼;另一方面,編譯器和處理器希望JMM對它們的束縛越少越好,這樣它們就可以最可能多的做優化來提高性能,希望的是一個弱的內存模型。

JMM考慮了這兩種需求,并且找到了平衡點,對編譯器和處理器來說,只要不改變程序的執行結果(單線程程序和正確同步了的多線程程序),編譯器和處理器怎么優化都行。

而對于程序員,JMM提供了happens-before規則(JSR-133規范),滿足了程序員的需求——簡單易懂,并且提供了足夠強的內存可見性保證。 換言之,程序員只要遵循happens-before規則,那他寫的程序就能保證在JMM中具有強的內存可見性。

JMM使用happens-before的概念來定制兩個操作之間的執行順序。這兩個操作可以在一個線程以內,也可以是不同的線程之間。因此,JMM可以通過happens-before關系向程序員提供跨線程的內存可見性保證。

happens-before關系的定義如下:

  1. 如果一個操作happens-before另一個操作,那么第一個操作的執行結果將對第二個操作可見(保障可見性),而且第一個操作的執行順序排在第二個操作之前(JMM對程序員做出的邏輯保障,并不是代碼指令真正的執行保障)。
  2. 即使兩個操作之間存在happens-before關系,并不意味著Java平臺的具體實現必須要按照happens-before關系指定的順序來執行。如果重排序之后的執行結果,與按happens-before關系來執行的結果一致,那么JMM也允許這樣的重排序。

因此,

第一條是JMM對程序員做出的邏輯保障;

第二條是JMM對編譯器、處理器進行重排序的約束原則:只有不改變程序的執行結果(不管是單線程還是多線程),編譯器、處理器怎么優化都可以。

happens-before關系本質上和as-if-serial語義是一回事。

as-if-serial語義保證單線程內重排序后的執行結果和程序代碼本身應有的結果是一致的,happens-before關系保證正確同步的多線程程序的執行結果不被重排序改變。

總之,如果操作A happens-before操作B,那么操作A在內存上所做的操作對操作B都是可見的,不管它們在不在一個線程。

天然的happens-before關系

在Java中,有以下天然的happens-before關系:

  • 程序順序規則:在一個線程內一段代碼的執行結果是有序的。就是還會指令重排,但是隨便它怎么排,結果是按照我們代碼的順序生成的不會變。
  • 監視器鎖規則:就是無論是在單線程環境還是多線程環境,對于同一個鎖來說,一個線程對這個鎖解鎖之后,另一個線程獲取了這個鎖都能看到前一個線程的操作結果。
  • volatile 變量規則:就是如果一個線程先去寫一個volatile變量,然后一個線程去讀這個變量,那么這個寫操作的結果一定對讀的這個線程可見。
  • 傳遞性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
  • start() 規則:在主線程A執行過程中,啟動子線程B,那么線程A在啟動子線程B之前對共享變量的修改結果對線程B可見。
  • join() 規則:在主線程A執行過程中,子線程B終止,那么線程B在終止之前對共享變量的修改結果在線程A中可見。也稱線程join()規則。

從 happens-before 的 volatile 變量規則可知,如果線程 A 寫入了 volatile 修飾的變量 V,接著線程 B 讀取了變量 V,那么,線程 A 寫入變量 V 及之前的寫操作都對線程 B 可見。

這里特別說明一下,happens-before 規則不是描述實際操作的先后順序,它是用來描述可見性的一種規則(前一個操作的結果對后續操作是可見的)。


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

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

相關文章

使用Java獲取本地PDF文件并解析數據

獲取本地文件夾下的PDF文件要獲取本地文件夾下的PDF文件,可以使用Java的File類和FilenameFilter接口。以下是一個示例代碼片段:import java.io.File; import java.io.FilenameFilter;public class PDFFileFinder {public static void main(String[] args…

吳恩達機器學習補充:決策樹和隨機森林

數據集:通過網盤分享的文件:sonar-all-data.csv 鏈接: https://pan.baidu.com/s/1D3vbcnd6j424iAwssYzDeQ?pwd12gr 提取碼: 12gr 學習來源:https://github.com/cabin-w/MLBeginnerHub 文末有完整代碼,由于這里的代碼和之前的按…

Shell腳本一鍵監控平臺到期時間并釘釘告警推送指定人

1. 監控需求客戶側有很多平臺需要定期授權,授權后管理后臺才可正常登錄,為避免授權到期,現撰寫腳本自動化監控平臺授權到期時間,在到期前15天釘釘或其他媒介提醒。2. 監控方案2.1 收集平臺信息梳理需要監控的平臺地址信息&#xf…

華為HCIE數通含金量所剩無幾?考試難度加大?

最近網上很火的一個梗——法拉利老了還是法拉利,這句話套在華為HCIE數通身上同樣適用,華為認證中的華為數通和云計算兩大巨頭充斥著大家的視野里面,也更加廣為人知,但隨著時代的發展,華為認證體系的調整,大…

#數據結構----2.1線性表

在數據結構的學習中,線性表是最基礎、最核心的結構之一 —— 它是后續棧、隊列、鏈表等復雜結構的 “基石”。今天從 “是什么”(定義)到 “怎么用”(基本操作),徹底搞懂線性表的核心邏輯。 一、先明確&…

2508C++,skia動畫

gif動畫原理 先了解一下gif動畫的原理: gif動畫由一系列靜態圖像(或叫幀)組成.這些圖像按特定的順序排列,每一幀都代表動畫中的一個瞬間,幀圖像是支持透明的. 每兩幀之間有指定的時間間隔(一般小于60毫秒),gif播放器每渲染一幀靜態圖像后,即等待此時間間隔,依此邏輯不斷循環渲染…

AI + 機器人:當大語言模型賦予機械 “思考能力”,未來工廠將迎來怎樣變革?

一、引言1.1 未來工廠變革背景與趨勢在科技飛速發展的當下,全球制造業正站在變革的十字路口。隨著消費者需求日益多樣化、市場競爭愈發激烈,傳統工廠模式的弊端逐漸顯現。生產效率低下、難以適應個性化定制需求、設備維護成本高昂且缺乏前瞻性等問題&…

pinia狀態管理的作用和意義

1. 什么是狀態管理 狀態管理就是統一管理應用中的數據,讓數據在多個組件之間共享和同步。 // 沒有狀態管理 - 數據分散在各個組件中 // 組件A const user ref({ name: 張三, age: 25 })// 組件B const user ref({ name: 張三, age: 25 }) // 重復定義// 組件C c…

十四、STM32-----低功耗

一、電源框圖VDDA 供電區域,主要是 ADC 電源以及參考電壓,STM32 的 ADC 模塊配備獨立的供電方 式,使用了 VDDA 引腳作為輸入,使用 VSSA 引腳作為獨立地連接,VREF 引腳為提供給 ADC 的 參考電壓。電壓調節器是 STM32 的…

一篇文章帶你徹底搞懂 JVM 垃圾收集器

垃圾收集器是 JVM 內存管理的執行引擎,負責自動回收無用的對象內存。其設計核心是 權衡:主要是吞吐量和停頓時間之間的權衡。沒有“最好”的收集器,只有“最適合”特定場景的收集器。一、核心基礎:分代收集模型主流 HotSpot JVM 采…

服務器排故隨筆:服務器無法ssh遠程登錄

文章目錄服務器排故隨筆:服務器無法遠程登錄問題現象解決過程第一步:確認故障描述是否準確第二步:確認網絡是否有問題第三步:確認ssh服務是否有問題第四步:確認防火墻是否放行sshd服務第五步:試試萬能的“重…

Deeplizard深度學習課程(六)—— 結合Tensorboard進行結果分析

前言 Tensorboard最初是tensorflow的可視化工具,被用于機器學習實驗的可視化,后來也適配了pytorch。Tensorboard是一個前端web界面,,能夠從文件里面讀取數據并展示它(比如損失、準確率、網絡圖)。具體使用可…

C語言————實戰項目“掃雷游戲”(完整代碼)

無論是找工作面試,還是課設大作業、考研,都離不開實戰項目的積累,如果你能把一個項目搞明白,并且給別人熟練的講出來,即使你沒有過項目經歷,也可以說是非常加分的,下面來沉浸式體驗一下這款掃雷…

數據結構之加餐篇 -順序表和鏈表加餐

目錄一、鏈表分割二、隨機鏈表的復制總結一、鏈表分割 鏈表分割 題目描述的意思就如下圖: 也就是把1,2挪到前面,6,3,5挪到后面,前者的相對順序不發生改變 這里要想往后挪就要先遍歷,遍歷到6…

JSP與Servlet整合數據庫開發:構建Java Web應用的全棧指南

JSP與Servlet整合數據庫開發:構建Java Web應用的全棧指南 概述 在Java Web開發領域,JSP(JavaServer Pages)與Servlet是構建動態Web應用的核心技術組合。Servlet作為Java EE的基礎組件,負責處理客戶端請求、執行業務邏…

設計五種算法精確的身份證號匹配

問題定義與數據準備 我們有兩個Excel文件: small.xlsx: 包含約5,000條記錄。large.xlsx: 包含約140,000條記錄。 目標:快速、高效地從large.xlsx中找出所有其“身份證號”字段存在于small.xlsx“身份證號”字段中的記錄,并將這些匹配的記錄保…

Spring 框架(IoC、AOP、Spring Boot) 的必會知識點匯總

目錄:🧠 一、Spring 框架概述1. Spring 的核心功能2. Spring 模塊化結構🧩 二、IoC(控制反轉)核心知識點1. IoC 的核心思想2. Bean 的定義與管理3. IoC 容器的核心接口4. Spring Bean 的創建方式🧱 三、AOP…

簡單工廠模式(Simple Factory Pattern)?? 詳解

?作者簡介:大家好,我是 Meteors., 向往著更加簡潔高效的代碼寫法與編程方式,持續分享Java技術內容。 🍎個人主頁: Meteors.的博客 💞當前專欄: 設計模式 ?特色專欄: 知識分享 &…

新電腦硬盤如何分區?3個必知技巧避免“空間浪費癥”!

剛到手的新電腦,硬盤就像一間空蕩蕩的大倉庫,文件扔進去沒多久就亂成一鍋粥?別急,本文會告訴你新電腦硬盤如何分區,這些方法不僅可以幫你給硬盤分區,還可以調整/合并分區大小等。所以,本文的分區…

【微知】git submodule的一些用法總結(不斷更新)

文章目錄綜述要點細節如何新增一個submodule?如何手動.gitmodules修改首次增加一個submodule?git submodule init,init子命令依據.gitmodules.gitmodules如何命令修改某個成員以及同步?如果submodule需要修改分支怎么辦&#xff1…