九、Java高級特性(ThreadLocal的使用和原理分析)

一、前言

我們在前面學習知道對象和變量的併發訪問我們可以使用synchronized、volatile關鍵字。synchronized保證了數據的原子性,解決併發訪問同一數據的時候排隊執行。volatile保證了數據的可見性,使得在多線程共享一個數據的時候,一旦數據發生改變。各個線程都能拿到最新的數據的之。
而ThreadLocal的出現使得每個線程都有自己的一份副本,也就是使得每個線程都單獨擁有自己的數據。達到線程之間的數據隔離。

二、ThreadLocal的使用

package com.it.test.thread;

public class ThreadLocalDemo {
    static ThreadLocal<Integer> local1 = new ThreadLocal<>() {
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    public static void main(String[] args) {
        new Thread1().start();
        new Thread2().start();
    }

   static class Thread1 extends Thread {
        @Override
        public void run() {

            local1.set(1);
            System.out.println(Thread.currentThread().getName()+":"+local1.get());

        }
    }

    static class Thread2 extends Thread {
        @Override
        public void run() {
            local1.set(2);
            System.out.println(Thread.currentThread().getName()+":"+local1.get());
        }
    }
}

Thread-0:1
Thread-1:2

三、ThreadLocal源碼分析

1、set方法

獲取當前線程,拿到當前線程的ThreadLocalMap ,如果map爲空,則創建一個map,否則之間給當前map設置值,key爲當前ThreacLocal對象,值就是我們存放的值。

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

來看下createMap方法,爲當前線程設置了一個ThreadLocalMap對象,

 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

來看下ThreadLocalMap的源碼,包含了一個靜態內部類Entry,繼承了弱引用的ThreadLocal。

   static class ThreadLocalMap {

  
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

在上面爲線程創建一個ThreadLocalMap對象的時候,調用了ThreadLocalMap的構造方法,在ThreadLocalMap的構造方法,構造方法傳入了:
ThreadLocal<?> firstKey, Object firstValue兩個參數。
接着內部創建了內部類的Entry類型數組,
table = new Entry[INITIAL_CAPACITY];
然後創建Entry對象,傳入firstKey和firstValue,存入到Entry數組中。
這裏需要注意的是,之所以創建了一個Entry數組,是因爲一個線程可能有多個ThreadLocal對象,
每一個ThreadLocal,都會在ThreadLocalMap內部的Entry數組存入。

   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);
        }

也就是當我們在某個線程裏通過ThreadLocal設置值的時候,那麼就會在當前線程中創建一個ThreadLocalMap,如果存在ThreadLocalMap則直接獲取當前線程的ThreadLocalMap。而ThreadLocalMap包含一個Entry數組,Entry有兩個屬性key和value。使用ThreadLocal設置值的時候,就會把ThreadLocal對象作爲key,以及具體設置的值,初始化給Entry對象,然後存入Entry類型的數組中。當有多個
ThreadLocal的時候,數組就會存入多個Entry對象。

2、get方法

   public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

首先拿到當前線程,然後獲取當前線程的ThreadLocalMap 。調用ThreadLocalMap 的getEntry方法。

   private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.refersTo(key))
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

我們知道ThreadLocalMap 中的包含了一個Entry數組,getEntry方法,傳入了ThreadLocal對象作爲key,然後經過系列的位運算,算出數組的下標。最好從數組中拿到Entry對象,

   ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }

拿到Entry對象,直接獲取Entry對象裏的value值,就是我們存入的value值。

小結

  • ThreadLocal使得每個線程擁有自己的一份副本,也就是擁有自己的一份數據。達到線程直接數據隔離
  • ThreadLocal的實現原理的核心就是:
    (1)當我們在某個線程下第一次使用ThreadLocal設置值的時候,那麼就會爲這個線程創建一個ThreadLocalMap 對象,ThreadLocalMap 中包含了一個Entry數組。Entry中包含了兩個屬性key和value。
    在設置值的時候,就會將我們的值存放到ThreadLocalMap 的Entry數組中,其中key爲當前ThreadLocal對象,值就是我們設置的值。
    (2)當我們使用ThreadLocal獲取值的時候,首先是會獲取當前線程的ThreadLocalMap ,然後獲取ThreadLocalMap 中的數組。接着通過當前ThreadLocal作爲key,通過位運算獲取到屬性key爲當前ThreadLocal對象的Entry對象,在獲取Entry中的value屬性。就是我們需要獲取的值。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章