JAVA篇:Java 多線程 (五)ThreadLocal詳解


5 ThreadLocal詳解

關鍵字:ThreadLocal、InheritableThreadLocal、ThreadLocal和局部變量

5.1 ThreadLocal

ThreadLocal是一個泛型類,java.lang.ThreadLocal<T>

這個類提供線程局部變量。可以將ThreadLocal定義為共享變量(全局變量或者static靜態變量),每個線程在訪問ThreadLocal變量(調用set/get)都會初始化屬於線程的變量副本,可以用於存儲一些與線程相關的狀態、用戶id、事務id等數據。

可以通過子類重寫initialValue()來設置初始值--默認初始值是null。

5.1.1 ThreadLocal應用

為線程設置遞增的線程id,存在於ThreadLocal變量threadids中。

    /* 測試ThreadLocal */public void test1(){
        // 原子整數,線程安全
        AtomicInteger nextInt = new AtomicInteger();
        // 設定threadids的初始值遞增
        ThreadLocal<Integer> threadids = new ThreadLocal<Integer>() {
            @Override
            protected Integer initialValue() {
                return nextInt.incrementAndGet();//返回遞增整數
            }
        };
​
        /* 創建5個子線程並輸出threadid的值 */
​
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+": "+threadids.get());//在這個時候調用初始化並返回threadid
​
            }
        };
        int threadNum = 5;
        Thread[] threads = new Thread[threadNum];
​
        for(int i=0;i<threadNum;i++){
            threads[i] = new Thread(runnable);
​
        }
        /*倒序啟動子線程*/
        for(int i=threadNum-1;i>=0;i--){
            threads[i].start();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
        System.out.println(Thread.currentThread().getName()+": "+threadids.get());//在這個時候調用初始化並返回threadid
​
    }

 

根據代碼結果可以看出來threadids中對某個線程初始化其對應值的時機是該線程調用threadids.get或者threadids.set的時候,所以有如下結果:

Thread-4: 1
Thread-3: 2
Thread-2: 3
Thread-1: 4
Thread-0: 5
main: 6

 

5.1.2 ThreadLocal.withInitial

Java8中ThreadLocal對象提供了一個Lambda構造方式,實現了非常簡潔的構造方法:withInitial

這個方法采用Lambda方式傳入實現了 Supplier 函數接口的參數。

        // 初始化1
        ThreadLocal<Integer> threadids1 = new ThreadLocal<>();
        System.out.println("使用new初始化的值:"+threadids1.get());
        //初始化2
        ThreadLocal<Integer> threadids2 = ThreadLocal.withInitial(()->100);
        System.out.println("使用withInitial初始化的值:"+threadids2.get());
        //初始化3
        ThreadLocal<HashMap<Integer,Integer>> threadids3 = ThreadLocal.withInitial(HashMap::new);
        System.out.println("使用withInitial初始化Map的值:"+threadids3.get());

 

使用new初始化的值:null
使用withInitial初始化的值:100
使用withInitial初始化Map的值:{}

 

5.2 InheritableThreadLocal

ThreadLocal在父線程、子線程之間是完全獨立不繼承的。

而InheritableThreadLocal給了子線程初始化繼承父線程ThreadLocal變量值,以及父線程操作子線程初始化值的方法。但僅僅是初始化值的繼承而已,InheritableThreadLocal對於線程依舊是線程獨享的,子線程更改值並不影響父線程。

子線程InheritableThreadLocal變量初始化的值默認繼承子線程運行時父線程的值,但是也可以通過重寫childValue方法來指定子線程初始值和父線程的值的關系。

    /* 測試InheritableThreadLocal */
    public void test3(){
        /* ThreadLocal */
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        /* 默認繼承父線程值的InheritableThreadLocal */
        InheritableThreadLocal<Integer> integerInheritableThreadLocal = new InheritableThreadLocal<>();
        /* 設置指定子線程值的InheritableThreadLocal */
        InheritableThreadLocal<Integer> integerInheritableThreadLocal2 = new InheritableThreadLocal<Integer>(){
            @Override
            protected Integer childValue(Integer value){
                return value+10;
            }
        };
​
                /* 主線程設置兩個值都為1 */
        threadLocal.set(1);
        integerInheritableThreadLocal.set(1);
        integerInheritableThreadLocal2.set(1);
​
        /* 子線程中獲取並更改值 */
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+":threadLocal= "+threadLocal.get());
                System.out.println(Thread.currentThread().getName()+":integerInheritableThreadLocal="+integerInheritableThreadLocal.get());
                System.out.println(Thread.currentThread().getName()+":integerInheritableThreadLocal2="+integerInheritableThreadLocal2.get());
                integerInheritableThreadLocal.set(2);
                integerInheritableThreadLocal2.set(2);
                System.out.println(Thread.currentThread().getName()+":設置 integerInheritableThreadLocal="+integerInheritableThreadLocal.get());
                System.out.println(Thread.currentThread().getName()+":設置 integerInheritableThreadLocal2="+integerInheritableThreadLocal2.get());
​
            }
        };
​
        /* 創建子線程1 */
        Thread t1 = new Thread(runnable);
        t1.start();
        
​
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        /* 子線程的數據更改不會影響主線程 */
        System.out.println(Thread.currentThread().getName()+":threadLocal= "+threadLocal.get());
        System.out.println(Thread.currentThread().getName()+":integerInheritableThreadLocal="+integerInheritableThreadLocal.get());
        System.out.println(Thread.currentThread().getName()+":integerInheritableThreadLocal2="+integerInheritableThreadLocal.get());
    }
​

Thread-0:threadLocal= null
Thread-0:integerInheritableThreadLocal=1
Thread-0:integerInheritableThreadLocal2=11
Thread-0:設置 integerInheritableThreadLocal=2
Thread-0:設置 integerInheritableThreadLocal2=2
main:threadLocal= 1
main:integerInheritableThreadLocal=1
main:integerInheritableThreadLocal2=1

 

5.3 ThreadLocal和局部變量

變量線程獨立,其實很容易想到局部變量。在一開始了解到ThreadLocal的定義時,我很難理解,因為我怎么想,這個定義都跟局部變量很像,我無法理解ThreadLocal設計出來的意義。

按照某個說法,可以將局部變量看做是把錢放在自己家里,ThreadLocal變量則是把錢放在銀行,雖然每個人各自的賬號及錢也只能自己訪問,但是錢放在一個同一的地方方便管理。這個區別不是共享變量和局部變量這樣子訪問權限上的區別,更大的區別在於一種設計上、代碼上更加簡單明了。

5.X 參考

Java中的ThreadLocal詳解

Java多線程9:ThreadLocal源碼剖析

Java多線程10:ThreadLocal的作用及使用

Java ThreadLocal變量 - 什么時候用以及如何使用?

ThreadLocal.withInitial

 

0、JAVA多線程編程

Java多線程編程所涉及的知識點包含線程創建、線程同步、線程間通信、線程死鎖、線程控制(掛起、停止和恢復)。之前 JAVA篇:Java的線程僅僅了解了部分線程創建和同步相關的小部分知識點,但是其實在編程過程中遇到的事情並不僅僅限於此,所以進行整理,列表如下:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM