Java線程本地存儲ThreadLocal

来源:https://www.cnblogs.com/aishangJava/archive/2019/07/30/11267562.html
-Advertisement-
Play Games

前言 ThreadLocal 是一種 無同步 的線程安全實現 體現了 Thread-Specific Storage 模式:即使只有一個入口,內部也會為每個線程分配特有的存儲空間,線程間 沒有共用資源 本文將總結 ThreadLocal 的用法與實現細節,希望能幫上忙 ThreadLocal 思維導 ...


前言

  • ThreadLocal 是一種 無同步 的線程安全實現
  • 體現了 Thread-Specific Storage 模式:即使只有一個入口,內部也會為每個線程分配特有的存儲空間,線程間 沒有共用資源
  • 本文將總結 ThreadLocal 的用法與實現細節,希望能幫上忙

ThreadLocal 思維導圖

線程安全 示意圖

1. 用法

ThreadLocal 的用法很簡單, ThreadLocal 提供了下列的public與protected方法,本文將引用 android.os.Looper.java 為例子講解 ThreadLocal 的用法:

ThradlLocal UML類圖

// /frameworks/base/core/java/android/os/Looper.java

public class Looper {

    // ...

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 設置線程局部變數的值
        sThreadLocal.set(new Looper(quitAllowed));
    }

    public static Looper myLooper() {
        // 獲取線程局部變數的值
        return sThreadLocal.get();
    }

    public static void prepare() {
        prepare(true);
    }

    // ...
}
  • ThreadLocal 為 static final變數 ,泛型參數為 Looper ,表示接受 Looper 類型的值
  • Looper#prepare() 中調用 ThreadLocal#set() 設置 變數的值,不同線程設置的值互不幹擾,不會相互覆蓋
  • Looper#myLooper() 中調用 ThreadLocal#get() 獲取 變數的值,不同線程獲取的值互不幹擾

Timethreads圖 - 01

  • 子類 重寫 ThreadLocal#initialValue() ,可以設置變數的初始值,我們查看相關源碼:

    public ConcurrentHashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
    }
    public ConcurrentHashMap(int initialCapacity,
                                 float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
        // Find power-of-two sizes best matching arguments
        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        this.segmentShift = 32 - sshift;//用於高位,判斷落在哪個Segment
        this.segmentMask = ssize - 1;//用於取模。之前在hashmap的indexFor方法有提過。2的n次方-1
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        while (cap < c)
            cap <<= 1;
        // create segments and segments[0]
        Segment<K,V> s0 =
            new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                             (HashEntry<K,V>[])new HashEntry[cap]);//初始化第一個位置的Segment
        Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];//初始化Segments
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }
    • 在 ThreadLocal#get() 中嘗試獲取變數的值,如果為空則會調用 ThreadLocal#setInitialValue() 設置設置初始值
    • 懶初始化 :直到第一次調用 get() 才會設置初值
    • 預設 :設置初始值為 null
  • ThreadLocal#remove() 用於 移除 變數之前存儲的值。如果在當前線程下次調用 ThreadLocal#get() 時,還沒有 set() 新的值,依舊會使用 ThreadLocal#setInitialValue() 設置初始值。

到這裡我們就可以總結 ThreadLocal 的生命周期,如下圖所示:

ThreadLocal生命周期 示意圖

2. 編程規約

記得嗎?《阿裡巴巴Java開發手冊》中提到過關於 ThreadLocal 的編程規約,如下所示:

  • 5.【強制】 SimpleDateFormate 是線程不安全的類,一般不要定義為 static 變數,如果定義為static,必須加鎖,或者使用 DateUtils 工具類

    正例:

    private static final ThreadLocal<DataFormat> df = new ThreadLocal<DateFormat>(){
          @Override
          protected DateFormat initialValue(){
                  return new SimpleDateFormat("yyyy-MM-dd");
          }
    };

    說明:如果是JDK8的應用,可以使用 Instant 代替 Date , LocalDateTime 代替 Calendar, DateTimeFormatter 代替 SimpleDateFormat ,官方給出的解釋:simple beautiful strong immutable thread-safe.

  • 15.【參考】(原文過於啰嗦,以下為筆者轉述) ThreadLocal 變數建議使用 static 修飾,可以保證變數在類初始化時創建,所有類實例可以共用同一個靜態變數。

    註意到了嗎?在文章開頭的Looper.java源碼中, ThreadLocal 變數就是使用 static 修飾的

看到這裡,相信你已經掌握了 ThreadLocal 的用法,下一篇文章將深入 ThreadLocal 的內部,探討數據結構的實現細節。

Segment

ConcurrentHashMap是由多個Segment組成的,Segment繼承了ReentrantLock,每次加鎖都是對某個Segment,不會影響其他Segment,達到了鎖分離(也叫分段鎖)的作用。

每個Segment又包含了HashEntry數組,HashEntry是一個鏈表。如下圖所示:

concurrencyLevel:併發數,預設16,直接影響segmentShift和segmentMask的值,以及Segment的初始化數量。Segment初始化的數量,為最接近且大於的辦等於2的N次方的值,比如concurrencyLevel=16,Segment數量為16,concurrencyLevel=17,Segment數量為32。segmentShift的值是這樣的,比如Segment是32,相對於2的5次方,那麼他的值就是32-5,為27,後面無符號右移27位,也就是取高5位的時候,就是0到31的值,此時Segment的下標也是0到31,取模後對應著每個Segment。segmentMask就是2的n次方-1,這邊n是5,用於取模。之前在hashmap的indexFor方法有提過。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 最近在做後臺管理項目,採用的 "vue element admin" ,上傳圖片是一個很常用的功能,也遇到了很多問題,剛好趁此機會做一些總結。 初步總結下會提到的問題,目錄如下: 1. el upload 自定義上傳方法 2. 圖片上傳到七牛雲 3. 圖片壓縮後再上傳(壓縮使用 "lrz" ) ...
  • transition組件可以給任何元素和組件添加進入/離開過渡,但只能給單個組件實行過渡效果(多個元素可以用transition-group組件,下一節再講),調用該內置組件時,可以傳入如下特性: name 用於自動生成CSS過渡類名 例如:name:'fade'將自動拓展為.fade-enter, ...
  • 摘要: 體驗神奇的GraphQL! 原文: "GraphQL 入門詳解" 作者:MudOnTire "Fundebug" 經授權轉載,版權歸原作者所有。 GraphQL簡介 定義 一種用於API調用的數據查詢語言 核心思想 傳統的api調用一般獲取到的是後端組裝好的一個完整對象,而前端可能只需要用其 ...
  • upitem() { console.log("1 :", 1); let user = this.username; let pwd = this.password; if (user == "" || pwd == "") { this.$toast("賬號或密碼不能為空!!"); } else... ...
  • 費話不多說,直接上問題: 1.開始時,頁面只有兩個DIV的嵌套(見圖) 運行結果是: 現在看運行的是正常的,但是當我設置讓 class="box2" 的DIV浮動時 運行結果是這樣的: 圖中可以看出,box1中已經沒有了任何高度,這是由於box2設置了浮動屬性,box2脫離了文檔流(也可以理解為bo ...
  • 這道題,是集數組,for迴圈和if語句一體的一道水題,首先用數組及for迴圈輸入10個蘋果的高度,然後………… ...
  • 架構雜談《八》 Docker 架構 一、Docker 引擎的三大組件 1)Docker 後臺服務(Docker Daemon):是長時間運行在後臺的守護進程,是Docker的核心服務,可以通過命令dockerd與它進行交互通信。 2)REST 介面(REST API):程式可以通過REST的介面來訪 ...
  • 一些 Spring Boot 小技巧、小知識點 初始化數據 我們在做測試的時候經常需要初始化導入一些數據,如何來處理呢?會有兩種選擇,一種是使用 Jpa,另外一種是 Spring JDBC 。兩種方式各有區別下麵來詳細介紹。 使用 Jpa 在使用 spring boot jpa的情況下設置 spri ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...