主頁 > 後端開發 > ThreadLocal的應用及原理

ThreadLocal的應用及原理

2023-05-26 07:45:35 後端開發

1. ThreadLocal 是什么

JDK 對ThreadLocal的描述為:

此類提供執行緒區域變數,這些變數與普通變數的不同之處在于,每個訪問一個變數的執行緒(通過其get或set方法)都有自己的、獨立初始化的變數副本,ThreadLocal 實體通常是類中的私有靜態欄位,這些欄位希望將狀態與執行緒(例如,用戶ID或事務ID)相關聯,

說白了,ThreadLocal就是用來存放執行緒自身相關資料的一個容器,這個容器叫做ThreadLocalMap,它是ThreadLocal的一個靜態內部類,同時作為Thread類的一個成員變數,ThreadLocal在使用時,先拿到當前執行緒的成員變數ThreadLocalMap,以當前的ThreadLocal物件作為key,變數作為value 存入ThreadLocalMap, 然后每個執行緒取變數都是從執行緒各自的ThreadLocalMap中取值,自然是執行緒安全的了,因為變數只在自己執行緒的生命周期內起作用,所以說ThreadLocal 提供執行緒區域變數,或者叫執行緒本地變數,

ThreadLocal 的特點有3個:

  1. 執行緒并發:在多執行緒并發的場景下使用,
  2. 資料傳遞:通過 ThreadLocal ,在同一個執行緒中,不同組件中傳遞公共變數,
  3. 執行緒隔離:不同執行緒之間互不干擾,這種變數在執行緒的生命周期內起作用,

2. ThreadLocal 怎么用

ThreadLocal 的常用方法有:

  1. public ThreadLocal():通過構造器創建物件,一般是靜態的,
  2. <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier):初始化一個 ThreadLcoal,
  3. void set(T value):設定當前執行緒系結的區域變數,
  4. T get():獲取當前執行緒系結的區域變數,
  5. void remove():洗掉當前執行緒系結的區域變數,

2.1 使用入門

2.1.1 原始版本

現在模擬一個需求,一個執行緒在業務開始時初始化一個用戶 id(類似在一次web請求中背景關系中初始化一下用戶資訊),業務結束時獲取這個用戶 id(比如用來列印日志,或者作為一個公共變數運用到業務編碼中),存在多個這樣的執行緒,

public class ThreadLocalTest {
    private String userId;

    private String getUserId() {
        return userId;
    }

    private void setUserId(String userId) {
        this.userId = userId;
    }

    public static void main(String[] args) {
        ThreadLocalTest test = new ThreadLocalTest();
        for (int i = 1; i < 6; i++) {
            Thread thread = new Thread(() -> {
                // 當前執行緒初始化userId
                test.setUserId(Thread.currentThread().getName() + "的userId");
                // 執行其他業務代碼
                System.out.println("===執行業務代碼===");
                // 當前執行緒獲取userId
                System.out.println(Thread.currentThread().getName() + "-->" + test.getUserId());
            });
            thread.setName("執行緒" + i);
            thread.start();
        }
    }
}

一種可能的結果:

===執行業務代碼===
執行緒2-->執行緒1的userId
===執行業務代碼===
執行緒1-->執行緒3的userId
===執行業務代碼===
執行緒3-->執行緒3的userId
===執行業務代碼===
執行緒4-->執行緒4的userId

由于執行緒調度的不確定性,可能執行緒1運行到一半,切換到了執行緒2,于是執行緒2獲取到的 userId 是執行緒1設定的,也就是說,每個執行緒之間的變數不是隔離的,造成資料錯誤,

2.1.2 ThreadLocal 版本

每個執行緒中的變數都存放到自己的執行緒當中,所以這些變數叫做執行緒區域變數很形象,

public class ThreadLocalTest {
    private static ThreadLocal<String> context = new ThreadLocal<>();

    private String getUserId() {
        return context.get();
    }

    private void setUserId(String userId) {
        context.set(userId);
    }

    public static void main(String[] args) {
        ThreadLocalTest test = new ThreadLocalTest();
        for (int i = 1; i < 5; i++) {
            Thread thread = new Thread(() -> {
                test.setUserId(Thread.currentThread().getName() + "的userId");
                System.out.println("===執行業務代碼===");
                System.out.println(Thread.currentThread().getName() + "-->" + test.getUserId());
                context.remove(); // 使用完清理執行緒區域變數
            });
            thread.setName("執行緒" + i);
            thread.start();
        }
    }
}

這樣每個執行緒就互不干擾,不會取錯變數值,一種可能的結果如下:

===執行業務代碼===
執行緒1-->執行緒1的userId
===執行業務代碼===
執行緒4-->執行緒4的userId
===執行業務代碼===
執行緒2-->執行緒2的userId
===執行業務代碼===
執行緒3-->執行緒3的userId

2.1.3 synchronized 版本

如果只看結果的正確性,用 synchronized 給業務代碼塊加鎖也是可以完成的,如下:

Thread thread = new Thread(() -> {
    synchronized (ThreadLocalTest.class) {
        test.setUserId(Thread.currentThread().getName() + "的userId");
        System.out.println("===執行業務代碼===");
        System.out.println(Thread.currentThread().getName() + "->" + test.getUserId());
    }
});

這樣完全可以實作需求,但是 synchronized 的問題是什么呢?我們總說誰誰誰是執行緒安全的類,因為它有 synchronized 修飾,就是因為 synchronized 讓多執行緒變成了單執行緒,它一次只允許一個執行緒執行,它能不安全嗎?但它帶來的代價是性能的下降,它不能并發執行,而 ThreadLocal 可以并發執行,

2.1.4 ThreadLocal 和 synchronized 對比

綜上,synchronized 和 ThreadLocal 兩個處理問題的角度和場景是不同的,

  • synchronized 的側重點在于保證操作的原子性,保證并發場景下共享變數的資料一致性,
  • ThreadLocal 強調執行緒隔離性,不同的執行緒互不干擾,保證并發場景下資料傳遞的正確性,在web請求背景關系中較為常見,

3. ThreadLocal 原理

3.1 代碼結構

ThreadLocal 的原理要從它的set(T value)get()方法的原始碼入手,在 set 值的時候,首先會獲取當前執行緒一個的成員變數ThreadLocalMapThreadLocalMap的 key 是當前ThreadLocal物件,value 是要存入的值,這個 key 和 value 會存到哪里呢?ThreadLocalMap還有個內部類Entry,這個Entry繼承了WeakReference,key 賦值給弱參考,也就是當前的ThreadLocal物件,value 則賦值給Entry的成員變數valueThreadLocalMap也是一個哈希表(所謂哈希表,也叫散串列,它基于陣列,通過某種哈希演算法計算出一系列關鍵字對應的散列值,然后以這些散列值作為陣列索引將資料存放到對應位置,達到快速查找的目的),它內部維護一個Entry陣列,來存盤鍵值對,存資料的時候也是通過哈希函式計算ThreadLocal 物件對應的陣列下標,然后放入Entry陣列中,

3.2 記憶體泄漏問題

ThreadLocal 會發生記憶體泄漏嗎?我們結合代碼慢慢分析,

在 2.1.1 節中有這樣的代碼:

public class ThreadLocalTest {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    private void setUserId(String userId) {
        threadLocal.set(userId);
    }
    // ...
}

首先,我們new了一個 ThreadLocal 物件,這里存在一個強參考:threadLocal參考變數指向 ThreadLocal 物件,其次,當其他執行緒執行setUserId方法時,ThreadLocal 的set方法最終是把資料存到了ThreadLocalMap中的Entry,看原始碼我們會發現,存資料最終是呼叫Entry的構造器Entry(ThreadLocal<?> k, Object v)完成的,而k這個引數是傳入的this物件,說明什么?我們使用 ThreadLocal 物件呼叫set,那this肯定是當前new出來的 ThreadLocal 物件!再次說明,我們new出來的 ThreadLocal 物件有兩個參考指向它:

  1. threadLocal變數的強參考,
  2. Entry中 key 的弱參考,

此時再看一張圖(這張圖被廣泛參考,感謝原圖作者??):

  1. 堆記憶體里面有個 ThreadLocal 物件,它被兩個箭頭指著,實線代表強參考,虛線代表弱參考,
  2. 有兩個參考鏈,一個是我們手動創建的threadLocal的參考變數指向的,即圖中的 ThreadLcoal Ref 對應示例代碼中的threadLocal變數;一個是由于呼叫了 ThreadLocal 的setget方法,初始化了當前執行緒的ThreadLocalMap,再初始化 Map 中的Entry物件,再初始化Entry物件中的 key 和 value,形成一個由當前執行緒物件到它內部變數的參考鏈,即上圖中的 Current Thread Ref,它對應set方法原始碼中的這一行Thread t = Thread.currentThread();中的變數t

那問題來了,如果這個手動創建的 ThreadLocal 物件 的『參考變數』被回收了,那 ThreadLocal 物件 是不是只剩下Entry中 key 的弱參考了?而弱參考的物件會隨時被 GC 回收,即Entry中的 key 會在 GC 后變為null了,我們知道,ThreadLocalMap的 key 是當前的 ThreadLocal 物件,那 key 為null了之后,就無法獲取到Entry,也取不到 value 的值了,在Entry物件沒有被主動洗掉,或者當前執行緒沒有終結的情況下,該Entry一直處在一個由當前執行緒指向的強參考鏈中,由于這個Entry獲取不到,就一直占用著記憶體,又因為強參考不能被 GC 回收,所以這個Entry就發生了記憶體泄漏,如果這個執行緒是一個普通執行緒,在執行緒終止的時候,整個執行緒物件被回收了,那記憶體泄漏的時間比較短;如果該執行緒一直不終止,比如執行緒池中的核心執行緒,那記憶體泄露問題就一直存在了,

注意,上面說的“如果這個手動創建的 ThreadLocal 物件 的『參考變數』被回收了”,應該會有人疑惑這種情況什么時候會發生呢?第一種情況,手動把這個參考變數置為null,雖然概率小,但也不是沒可能;第二種情況,參考變數是存在堆疊記憶體中,當方法執行完,就會立即回收堆疊記憶體中的參考變數,即堆記憶體中的實際物件失去參考指標了,這種情況就比如 ThreadLocal 是在方法中創建的區域變數,

3.3 為什么使用弱參考

Entry的 key 使用弱參考有記憶體泄漏風險,那為什么 JDK 還是使用弱參考而不是強參考?

我們分兩種情況討論:

  • key 使用強參考:ThreadLocal 的參考變數被回收了,這句話意味著什么呢?參考變數被回收了,意味著代碼中不再使用 ThreadLocal 這個物件了,因為要使用 ThreadLocal 這個物件,我們需要用它的參考變數取調setget方法,現在參考變數沒了,我們就用不了 ThreadLocal 這個物件了,但問題是,ThreadLocalMap還持有ThreadLocal物件的強參考,當前執行緒到Entry的強參考鏈依然存在,注意,前面提到了,ThreadLocal 物件已經不再使用了,也就是說Entry就獲取不到了,如果Entry沒有手動洗掉,或者執行緒沒有結束,這個沒用的Entry也會一直保留,依然發生記憶體泄漏(要明白記憶體泄漏是物件沒用了,還存在記憶體中不被回收的情況),
  • key 使用弱參考:前面已經分析過了,ThreadLocal 的參考變數被回收了,ThreadLocal物件也被回收,導致Entry的 key 變成null,在沒有手動洗掉Entry或執行緒不結束時依然發生記憶體泄漏,

歸根結底,由于ThreadLocalMap的生命周期跟Thread一樣長,在 ThreadLocal 的參考變數消失后,如果執行緒不結束,原來的Entry就不會回收,這就是記憶體泄漏的本質,雖然 ThreadLocal 在每次讀寫資料的時候,都會將keynullEntry清空,但是,既然 ThreadLocal 的參考變數都消失了,我們也沒機會再setget了,

那為什么使用弱參考?我也不知道!我還沒想明白,如果正在閱讀的你知道,請你告訴我下,謝謝??,雖然ThreadLocalMap的注釋中解釋了:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.

為了幫助處理非常大和長期的使用,哈希表條目使用WeakReferences作為鍵,

我覺得沒必要取糾結這個問題,只要規范的使用 ThreadLocal,幾乎不會發生記憶體泄漏,

3.4 如何防止記憶體泄漏

  1. 把 ThreadLocal 物件申明為類變數,類變數的生命周期跟 JVM 是同步的,這樣 ThreadLocal 的強參考就一直存在,不會被 GC 回收,Entrykey就不會發生null的情況了,
  2. 使用完 ThreadLocal 后,用remove()方法,清空當前ThreadLocal 對應的資料,對應的Entry就不占記憶體了,

第一種情況雖熱能避免Entry的key為null的情況,但是如果后續執行緒不再訪問這個 key,且執行緒不結束時,這個 key 對應的資料也會一直存在記憶體中,容易造成記憶體溢位的問題,所以最好的辦法就是在 ThreadLocal 使用完之后,使用remove()方法清除資料,

4. ThreadLocal 如何存多個變數

上面的示例代碼中,ThreadLocal 只存了一個變數,實際情況不可能只存一個吧,多個變數如何存,如何取?

要知道 ThreadLocal 使用set方法存資料時,key 用的this物件,就是當前正在使用的 ThreadLocal 物件,說明一個 ThreadLocal 物件,在一個執行緒中,只能存一個執行緒本地變數,多個執行緒雖然都是用的是一個 key,但是不同的執行緒用的是不同的ThreadLocalMap

第一種方案是多 new 幾個 ThreadLocal 物件,每個 ThreadLocal 物件對應一個業務變數,

第二種方法就是在給 ThreadLocal 初始化一個HashMap,這是最常規的做法,比如下面:

public class ThreadLocalTest {
    private static final ThreadLocal<Map<String, Object>> context =
            ThreadLocal.withInitial(HashMap::new);

    private String getUserId() {
        return String.valueOf(context.get().get("userId"));
    }

    private void setUserId(String userId) {
        context.get().put("userId", userId);
    }

    public void setUserName(String userName) {
        context.get().put("userName", userName);
    }

    public String getUserName() {
        return String.valueOf(context.get().get("userName"));
    }

    public static void main(String[] args) {
        ThreadLocalTest test = new ThreadLocalTest();
        for (int i = 1; i < 5; i++) {
            Thread thread = new Thread(() -> {
                String threadName = Thread.currentThread().getName();
                test.setUserId(threadName + "的userId");
                test.setUserName(threadName + "的userName");
                System.out.println("===執行業務代碼===");
                System.out.println(threadName + "-->" + test.getUserId() + "," + test.getUserName());
            });
            thread.setName("執行緒" + i);
            thread.start();
        }
    }
}

一種可能的結果:

===執行業務代碼===
執行緒2-->執行緒2的userId,執行緒2的userName
===執行業務代碼===
執行緒4-->執行緒4的userId,執行緒4的userName
===執行業務代碼===
執行緒3-->執行緒3的userId,執行緒3的userName
===執行業務代碼===
執行緒1-->執行緒1的userId,執行緒1的userName

5. 為什么用 ThreadLocal

5.1 ThreadLocal的使用場景

執行緒的背景關系傳遞,企業中最常見的是應用到web請求的背景關系,一個 Http 請求會經過一系列攔截器,過濾器最后到達服務層,在這個呼叫鏈路中,會頻繁的使用到一些公共資料,如用戶資訊或請求的ID,把這些公共資料放到 ThreadLocal 中,會在請求的鏈路中非常方便的使用這些資訊,

還有一些框架中會使用 ThreadLocal 來管理資料庫連接,避免了執行緒之間的競爭,比如 Mybatis 就是用 ThreadLocal 來存盤Sqlsession物件,

5.2 使用 ThreadLocal 的好處

使用 ThreadLocal 的好處是并發場景下減少了同一個執行緒內多個函式或組件之間傳遞公共變數的復雜度,且提高了使用這些共享變數的安全性,

本文來自博客園,作者:xfcoding,轉載請注明原文鏈接:https://www.cnblogs.com/cloudrich/p/17431133.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553405.html

標籤:其他

上一篇:Java的CompletableFuture,Java的多執行緒開發

下一篇:返回列表

標籤雲
其他(159693) Python(38169) JavaScript(25452) Java(18129) C(15231) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7211) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5341) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4576) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1976) 功能(1967) Web開發(1951) HtmlCss(1944) C++(1922) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1861) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • ThreadLocal的應用及原理

    ## 1. ThreadLocal 是什么 JDK 對`ThreadLocal`的描述為: > 此類提供執行緒區域變數。這些變數與普通變數的不同之處在于,每個訪問一個變數的執行緒(通過其get或set方法)都有自己的、獨立初始化的變數副本。ThreadLocal 實體通常是類中的私有靜態欄位,這些欄位希 ......

    uj5u.com 2023-05-26 07:45:35 more
  • Java的CompletableFuture,Java的多執行緒開發

    # 三、Java8的CompletableFuture,Java的多執行緒開發 ## 1、CompletableFuture的常用方法 - 以后用到再加 ```properties runAsync() :開啟異步(創建執行緒執行任務),無回傳值 supplyAsync() :開啟異步(創建執行緒執行任務 ......

    uj5u.com 2023-05-26 07:35:06 more
  • Maven的核心解壓與配置

    ? # Maven的核心解壓與配置 @[toc] ## 1. Maven 官網地址 首頁:[Maven – Welcome to Apache Maven(opens new window)](https://maven.apache.org/) ![在這里插入圖片描述](https://img20 ......

    uj5u.com 2023-05-26 07:29:23 more
  • AQS原始碼解讀----AbstractQueuedSynchronizer

    36 package cn.com.pep; 37 import java.util.concurrent.TimeUnit; 38 import java.util.concurrent.locks.AbstractOwnableSynchronizer; 39 import java.util. ......

    uj5u.com 2023-05-26 07:28:40 more
  • < Python全景系列-7 > 提升Python編程效率:模塊與包全面解讀

    Python全景系列的第七篇,本文將深入探討Python模塊與包的基本概念,使用方法以及其在實際專案中的應用。我們也會揭示一些鮮為人知,卻又實用的技術細節。 ......

    uj5u.com 2023-05-26 07:28:06 more
  • Netty實戰(三)

    [toc](目錄) # 一、Channel、EventLoop 和 ChannelFuture 上一篇博文我們在構建服務端和客戶端中出現了一些新的類,可能有些同學還有些不了解它們的具體功能。沒關系,接下來我們對于 Channel、EventLoop 和 ChannelFuture 類進行的討論增添更 ......

    uj5u.com 2023-05-25 12:11:19 more
  • MyBatis體系筆記(未完結)

    MyBatis 什么是MyBatis MyBatis是優秀的持久層框架 MyBatis使用XML將SQL與程式解耦,便于維護 MyBatis學習簡單,執行高效,是JDBC的延伸 1.MyBatis開發流程 引入MyBatis依賴 創建核心組態檔 創建物體(Entity)類 創建Mapper映射檔案 ......

    uj5u.com 2023-05-25 11:58:15 more
  • springboot~統一處理日期請求引數java.utils.Date和java.time.Lo

    日期型別的引數在從前端通過url引數傳遞到后端時,它會被進行格式化,如果格式化失敗會出現400的錯誤,像日期格式默認會使用yyyy/MM/dd的格式,如果希望自己去個性化配置,我們可以通過實作WebMvcConfigurer介面的addFormatters方法來完成。 # java.time.Loc ......

    uj5u.com 2023-05-25 11:38:09 more
  • JavaWeb編程面試題——導航

    面試題==知識點,這里所記錄的面試題并不針對于面試者,而是將這些面試題作為技能知識點來看待。不以刷題進大廠為目的,而是以學習為目的。這里的知識點會持續更新,目錄也會隨時進行調整。 ......

    uj5u.com 2023-05-25 11:37:48 more
  • spring-transaction原始碼分析(5)TransactionInterceptor事務攔

    spring-tx的事務攔截邏輯在TransactionInterceptor類,本文將詳細分析其實作方式。 # 事務攔截器TransactionInterceptor spring-tx的事務攔截邏輯在TransactionInterceptor類,它實作了MethodInterceptor介面。 ......

    uj5u.com 2023-05-25 11:37:32 more