1. 程式人生 > >入坑JAVA多執行緒併發(八)詳解ThreadLocal使用和原理

入坑JAVA多執行緒併發(八)詳解ThreadLocal使用和原理

  ThreadLocal是一個用於儲存多執行緒變數的類,它可以把執行緒與設定的值對應起來,因為它為變數在每個執行緒都建立了一個副本。訪問的時候每個執行緒只能訪問到自己的副本變數。

例項

看如下程式碼:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        ThreadLocal<String> threadLocal = new ThreadLocal();
        Thread1 thread1 = new Thread1("執行緒A"
,threadLocal); Thread1 thread2 = new Thread1("執行緒B",threadLocal); Thread1 thread3 = new Thread1("執行緒C",threadLocal); Thread1 thread4 = new Thread1("執行緒D",threadLocal); thread1.start(); thread2.start(); thread3.start(); thread4.start(); } } class Thread1 extends Thread{ ThreadLocal<String> threadLocal; public
Thread1(String name,ThreadLocal<String> threadLocal){ super(name); this.threadLocal = threadLocal; } @Override public void run() { try { for(int i = 0;i< 10;i++){ threadLocal.set(getName()+i); System.out.println(getName()+": "
+threadLocal.get()); Thread.sleep(200); } } catch (InterruptedException e) { e.printStackTrace(); } } }

輸出為:

執行緒B: 執行緒B0
執行緒D: 執行緒D0
執行緒A: 執行緒A0
執行緒C: 執行緒C0
執行緒C: 執行緒C1
執行緒A: 執行緒A1
執行緒B: 執行緒B1
執行緒D: 執行緒D1
執行緒D: 執行緒D2
執行緒C: 執行緒C2
執行緒A: 執行緒A2
執行緒B: 執行緒B2
執行緒B: 執行緒B3
執行緒D: 執行緒D3
執行緒A: 執行緒A3
執行緒C: 執行緒C3
執行緒C: 執行緒C4
執行緒A: 執行緒A4
執行緒B: 執行緒B4
執行緒D: 執行緒D4

可以看到效果和預期是一樣的,每個執行緒都只是獲取自己設定的值。

原理

每個執行緒內部儲存著一個ThreadLocalMap 例項的,ThreadLocalMap 例項儲存著一個或者多個ThreadLocal例項,獲取的時候就可以根據當前執行緒獲得對應設定的值
ThreadLocal的構造方法只有一個預設的無參構造方法;
set方法:

    public void set(T value) {
        Thread t = Thread.currentThread();   //獲取當前執行緒
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    //獲取Map進行設值,如果沒有就新建一個map
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;           //Thread的一個屬性,初始值為NUll;
     }

    //這個是Thread的程式碼,每個Thread儲存一個map的
    ThreadLocal.ThreadLocalMap threadLocals = null;

    //新建一個Map,ThreadLocalMap是內部類
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);   //設定值
    }

    //ThreadLocalMap的構造方法
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
   }

可以看出這裡是把每個執行緒的threadLocals 設定為一個ThreadLocalMap 的例項,ThreadLocalMap 儲存的是key是threadLocals例項,value是對應的值
如下圖:
這裡寫圖片描述
每個執行緒都有一個threadLocals ,指向對應的ThreadLocalMap 例項,ThreadLocalMap 是一個map結構,ThreadLocalMap 裡面每個Entry儲存著一個對應的value值,Entry例項的key是ThreadLocal例項,value是當前執行緒在這個ThreadLocal設定的value值;如果對map結果不熟,可參考Java從入門到放棄(十)集合框架之HashMap原始碼(1);

ThreadLocalMap的Hash,ThreadLocalMap的key值取的是ThreadLocal例項,這裡hash和hashmap有一些不同:

    private final int threadLocalHashCode = nextHashCode();  //hash值

    private static AtomicInteger nextHashCode =
        new AtomicInteger();                   //原子操作類
    }
    private static final int HASH_INCREMENT = 0x61c88647;    //每次hash間隔

    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);     //獲取下一個hash值
    }

這裡是把hash值按照HASH_INCREMENT 的間隔相加。這樣子可以保證不會出現相同hash的情況,避免Entry陣列的Entry太過於集中,讓Entry均勻分佈。(這個數字可以讓計算出來的索引比較均勻分佈)
get方法

    public T get() {
        Thread t = Thread.currentThread();  //獲取當前執行緒
        ThreadLocalMap map = getMap(t);    //獲取map
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);  //獲取對應Entry
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();    //沒有就返回初始化的值
    }
    //設定初始化的值
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    //初始化
    protected T initialValue() {
        return null;
    }

這裡就是獲取當前執行緒,取得執行緒的map,根據Threadlocal為key找到對應的value值。如果沒有設定就初始化並且設定對應值為null;

remove方法

    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
    }

這個也很簡單,就是獲取對應的map然後刪除。