Java 併發編程:ThreadLocal 的使用及其源碼實現

一、ThreadLocal的使用

防止任務在共享資源上產生衝突的一種方式是根除對變量的共享,使用線程的本地存儲爲使用相同變量的不一樣線程建立不一樣的存儲。java

下面是一個 ThreadLocal 的實例。這裏咱們使用了靜態的全局變量 ThreadLocal 對象來保存 Integer 類型的值。咱們在不一樣的線程中將指定的數字傳入到 threadLocal 中進行保存。而後,再將其讀取出來:數據庫

private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

    public static void main(String...args) {
        threadLocal.set(-1);
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i=0; i<5; i++) {
            final int ii = i; // i不能是final的,建立臨時變量
            executor.submit(new Runnable() {
                public void run() {
                    threadLocal.set(ii);
                    System.out.println(threadLocal.get());
                }
            });
        }
        executor.shutdown();
        System.out.println(threadLocal.get());
    }
複製代碼

從程序的執行結果能夠看出,每一個線程都正確地讀取出來了保存到 ThreadLocal 中的數據。數組

因此,咱們總結一下 ThreadLocal 的做用就是,存儲在 ThreadLocal 中的變量是線程安全的,每一個線程只能讀取出本身存儲的值。安全

一般它的使用方式就是定義一個靜態全局的 ThreadLocal 實例,而後每一個線程使用它來讀寫只有本身會用到的數據。好比,咱們要爲每一個線程建立了一個數據庫鏈接,而且該鏈接只容許該線程本身使用,那麼能夠將它存儲在 ThreadLocal 中,而後在用到的地方獲取。數據結構

看了上面的例子,也許你會又如下一些問題:this

  1. ThreadLocal中存儲的值是如何保證絕對的線程安全的?
  2. 那麼這些值是存儲在什麼地方?
  3. 是靜態類型的仍是實例類型的?
  4. 若是某個線程執行完畢了,被銷燬了,那麼這些存儲的值會被怎麼處理呢?
  5. ……

帶着上面的這些問題,咱們來看下在JDK源碼中 ThreadLocal 是如何實現的。spa

二、ThreadLocal的做用原理

咱們仍是先從讀取的操做來看。線程

如下是 ThreadLocalset() 方法的代碼:code

public T get() {
        Thread t = Thread.currentThread();  // 1
        ThreadLocalMap map = getMap(t);  // 2
        if (map != null) {  // 3
            ThreadLocalMap.Entry e = map.getEntry(this);  // 4
            if (e != null) {
                T result = (T) e.value; // 5
                return result;
            }
        }
        return setInitialValue();
    }
複製代碼

這裏首先會再步驟1中獲取到當前線程的實例,而後在步驟2中經過getMap()方法,使用當前的線程的ThreadLocalMap。這裏的ThreadLocalMap的定義以下:cdn

static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
		
        private Entry[] table;
		
        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);
        }
		
        // ...
    }
複製代碼

而後,咱們看下getMap()方法的定義:

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
複製代碼

也就是說實際上當咱們調用 get() 方法的時候,會先獲取當前線程中的 threadLocals 字段,該字段是 ThreadLocalMap 類型的。而後,咱們使用當前的 ThreadLocal 實例做爲鍵來從哈希表中獲取到一個 Entry,而實際的值就保存再 Entryvalue 字段中。

就像上面的 getEntry() 方法定義的那樣,彷佛這裏的哈希表只是一個數組,那哈希衝突是怎麼解決的呢?實際上,咱們知道一般解決哈希衝突有兩種解決方式,一種是拉鍊法,一種是線性探測法。前者在 HashMapConcurrentHashMap 中使用較多,而這裏用到的其實就是線性探測法。說白了就是將全部的值放在一個數組裏面而後根據散列的結果到數組中取值,具體的實現方式能夠看相關的數據結構知識點。

這裏的關係是否是有點亂,咱們來捋一下:

咱們使用ThreadLocal存儲的值實際是存儲在Thread使用ThreadLocalMap當中的,而這裏的ThreadLocal實例值起到了一個哈希表的鍵的做用:

ThreadLocal

就像上圖顯示的那樣,假如咱們在線程thread1中調用了threadLocal1get()方法,首先會用Thread.currentThread()方法獲取到thread1,而後獲取到thread1threadLocals實例,threadLocals是一個ThreadLocalMap類型的哈希表。而後,咱們再用threadLocal1做爲鍵來從threadLocals中獲取到值Entry,並從Entry中取出存儲的值並返回。

至此,咱們已經瞭解了ThreadLocal的實現的原理,原本想看下set()方法的,可是到此已經基本真相大白了,因此也就沒有繼續下去的必要了。

三、總結

咱們回過頭來看下以前提出的幾個問題:

  1. ThreadLocal中存儲的值是如何保證絕對的線程安全的? 實際上每一個值都是存在線程內部的,ThreadLocal只用來幫助咱們從該線程內部的哈希表中找到存放的那個值。
  2. 那麼這些值是存儲在什麼地方?線程內部的實例字段。
  3. 是靜態類型的仍是實例類型的?線程內部的實例字段。
  4. 若是某個線程執行完畢了,被銷燬了,那麼這些存儲的值會被怎麼處理呢?由於是線程的局部字段,因此線程不在了,值就沒有了。

以上就是ThreadLocal的用法和實現原理。

相關文章
相關標籤/搜索