ThreadLocal作用以及原理解析


ThreadLocal作用

對於多個線程訪問一個共享變量的時候,我們往往要通過加鎖的方式進行同步,像這樣

但是除此之外,其實還有另一種方式可以隔絕線程對於共享變量讀寫的獨立性。那就是ThreadLocal。如果你創建了一個ThreadLocal變量,那么訪問這個變量的每個線程都會有一塊獨立的空間,當多個線程操作這個變量的時候,實際上操作的都是自己線程所屬的空間的那個變量,不會對其他線程有影響,也不會被其他線程影響,因為彼此都是互相獨立的。因此想要保證線程安全,也可以把共享變量放在ThreadLocal中。總體來說就是,ThreadLocal提供了線程內存儲變量的能力,這些變量不同之處在於每一個線程讀取的變量是對應的互相獨立的。通過get和set方法就可以得到當前線程對應的值。

接下來看一個例子

public class ThreadLocalDemo1 {

    public static int value = 0;
   static ThreadLocal<Object> local = new ThreadLocal<>();
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"---線程初始值:"+local.get());
                    local.set("我是"+Thread.currentThread().getName());
                    System.out.println(Thread.currentThread().getName()+"---線程修改值:"+local.get());
                }
            },"線程"+i).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主線程"+local.get());
    }

}

運行結果

線程1---線程初始值:null
線程4---線程初始值:null
線程3---線程初始值:null
線程0---線程初始值:null
線程2---線程初始值:null
線程0---線程修改值:我是線程0
線程3---線程修改值:我是線程3
線程4---線程修改值:我是線程4
線程1---線程修改值:我是線程1
線程2---線程修改值:我是線程2
主線程null

上面這段代碼,運行結果印證了我們開頭說的那些關於ThreadLocal的論述,分析一下代碼,可以看到,我們這里只有一個ThreadLocal對象,即local,我們一共有五個線程,線程0對local進行set值之后,線程2再get卻還是null,但是線程0自己再get,卻可以拿到自己設置的那個值 我是線程0 ,別的線程是拿不到這個值的,而且代碼的最后,在所有的線程都運行完畢之后,在主線程對local進行get操作,拿到的值卻還是null。這就印證了這個結論,每個線程都只能拿到自己線程所屬的值,線程之間是相互獨立的

ThreadLocal原理

set方法

話不多說,我們直接上源碼。首先先看看ThreadLocal的set方法

public void set(T value) {
    //先獲取當前線程
        Thread t = Thread.currentThread();
    //看看當前線程是不是已經設置過值了
        ThreadLocalMap map = getMap(t);
    //如果設置過了,就覆蓋原來的值
        if (map != null)
            map.set(this, value);
        else
            //如果當前線程第一次設置值 那就創建一個存儲區域(Map)
            createMap(t, value);
    }

繼續看看 createMap(t, value)方法的源碼

 /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

根據注釋,和源碼里面可以看到,createMap方法里面實際上就是new了一個ThreadLocalMap對象,創建了一個與ThreadLocal關聯的map,然后把當前Thread中的一個ThreadLocal引用和要設置的值放進去。請注意t.threadLocals是定義在Thread類里的,代碼如下

  //這一段是定義在Thread類中的
ThreadLocal.ThreadLocalMap threadLocals = null;

繼續看看ThreadLocalMap里有啥

  /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        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);
        }

可以看到,ThreadLocalMap里維護了一個Entry數組,將Thread里面的ThreadLocal.ThreadLocalMap變量引用並且通過一系列的hashcode操作,作為key。接下來繼續看看Entry

    /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

Entry繼承WeakReference,使用弱引用,可以將ThreadLocal對象的生命周期和線程生命周期解綁,持有對ThreadLocal的弱引用,可以使得ThreadLocal在沒有其他強引用的時候被回收掉,這樣可以避免因為線程得不到銷毀導致ThreadLocal對象無法被回收。

以上就差不多是整個set方法的源碼了,有了以上的了解,再看get的就會很簡單

get方法

  public T get() {
      //獲取當前線程,並以線程引用為key去ThreadLocalMap中獲取值
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
      //如果為不為空,就以當前線程對應的Thread中的ThreadLocal引用 進行hash計算 拿到值返回
        if (map != null) {
            //拿到引用去獲取值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
      //為空就返回一個初始值
        return setInitialValue();
    }

//對應getMap方法 返回的是當前線程的threadLocals引用
  ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
//上面的getEntry方法
      private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

//get中的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;
    }


可以看到,如果你一個線程在get之前沒有set,也是會給你以當前線程創建一個與你線程對應的createMap的,只不過key是當前線程下的ThreadLocalMap引用,value為null,因為

 T value = initialValue();

這一行的的initialValue()方法是這樣的

  protected T initialValue() {
        return null;
    }

其實還有remove方法啥的,原理都差不多.。所以就不贅述了。

總結

ThreadLocal為每個線程都定義了一個ThreadLocalMap類型名為threadLocals的變量,有點類似於HashMap,但是嚴格意義上來說不是,只是將其類比為Map結構,key為當前線程中的threadLocals的this引用,value是設置的值。所以每個線程都有不同的key,所能獲取到的值也是不一樣的,就是利用這種思想去保證值對每個線程的獨立性。因為不管是get還是set之前都會有currentThread這個操作,所以每個線程都只能取到自己線程對應的值。


免責聲明!

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



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