Java併發編程系列之六:深入理解ThreadLocal

引言

無論實際項目實戰還是面試,ThreadLocal都是一個繞不開的話題,本文主要從源碼角度和大家一起探討下ThreadLocal的神祕面紗。

  • ThreadLocal是什麼?它能幹什麼?
  • ThreadLocal源碼分析
  • 總結

一、ThreadLocal是什麼?它能幹什麼?

ThreadLocal 是一個線程的本地變量, 也就意味着這個變量是線程獨有的,是不能與其他線程共享的,它並不是解決多線程共享變量的問題。

所以ThreadLocal與線程同步機制不同,線程同步機制是多個線程共享同一個變量,而ThreadLocal是爲每一個線程創建一個單獨的變量副本,故而每個線程都可以獨立地改變自己所擁有的變量副本,而不會影響其他線程所對應的副本。可以說ThreadLocal爲多線程環境下變量問題提供了另外一種解決思路。

ThreadLocal的思想就是用空間換時間,使各線程都能訪問屬於自己這一份的變量副本,變量值不互相干擾,減少同一個線程內的多個函數或者組件之間一些公共變量傳遞的複雜度。

在這裏插入圖片描述


二、ThreadLocal源碼分析

1、ThreadLocalMap解析
ThreadLocal內部定義了一個ThreadLocalMap的內部類,ThreadLocalMap實際利用Entry來實現key-value的存儲,如下所示:

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是實現線程隔離機制的關鍵,從以上代碼可以看出Entrykey就是ThreadLocal,而value就是值。同時,Entry也繼承WeakReference,所以說Entry所對應keyThreadLocal實例)的引用爲一個弱引用。

我們主要來看下核心的getEntry()set(ThreadLocal> key, Object value)方法

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);
        }
 private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            // 根據 ThreadLocal 的散列值,查找對應元素在數組中的位置
            int i = key.threadLocalHashCode & (len-1);

			// 採用“線性探測法”,尋找合適位置
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
				// 若key 存在,直接覆蓋
                if (k == key) {
                    e.value = value;
                    return;
                }
				// key == null,但是存在值(因爲此處的e != null),說明之前的ThreadLocal對象已經被回收了
                if (k == null) {
                	// 用新元素替換陳舊的元素
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			//創建新元素
            tab[i] = new Entry(key, value);
            int sz = ++size;
            // 如果沒有清理陳舊的 Entry 並且數組中的元素大於了閾值,則進行 rehash
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

2、核心方法解析
(1) get()
返回此線程局部變量的當前線程副本中的值

public T get() {
		//獲取當前線程
        Thread t = Thread.currentThread();
        //獲取當前線程的成員變量 threadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	// 從當前線程的ThreadLocalMap獲取相對應的Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 獲取目標值
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

首先通過當前線程獲取所對應的成員變量ThreadLocalMap,然後通過ThreadLocalMap獲取當前ThreadLocalEntry,最後通過所獲取的Entry獲取目標值result

(2) set(T value)
將此線程局部變量的當前線程副本中的值設置爲指定值。

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

獲取當前線程所對應的ThreadLocalMap,如果不爲空,則調用ThreadLocalMapset()方法,key就是當前ThreadLocal,如果不存在,則調用createMap()方法新建一個。

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

(3) initialValue()
返回此線程局部變量的當前線程的初始值。

protected T initialValue() {
        return null;
    }

該方法定義爲protected級別且返回爲null,需要其子類實現其功能,所以我們在使用ThreadLocal的時候一般都應該覆蓋該方法。該方法不能顯示調用,只有在第一次調用get()或者set()方法時纔會被執行,並且僅執行1次。

(4) remove()
移除此線程局部變量當前線程的值。

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

三、總結

1、多個線程去獲取一個共享變量時,要求獲取的是這個變量的初始值的副本。每個線程存儲這個變量的副本,對這個變量副本的改變不會影響變量本身。適用於多個線程依賴不同變量值完成操作的場景。比如:

  • 多數據源的切換
  • spring聲明式事務

2、將ThreadLocal設置成private static的,這樣ThreadLocal會盡量和線程本身一起回收。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章