通俗易懂弄清ThreadLocal內部原理

前言

ThreadLocal是JDK中的一個類,不少基礎框架和平時開發中都會使用到,所以有必要弄清其內部原理,才能更好地使用它。java

使用方法

要弄清原理,仍是要先知道如何使用,ThreadLocal用起來是很簡單的,通常都是把ThreadLocal定義爲static變量,也就是隻有一個實例對象,以下:git

private static ThreadLocal<Integer> sThreadLocal = new ThreadLocal<Integer>();
複製代碼

而後就能夠在各個線程中使用這個sThreadLocal了:github

for (int i = 0; i < 5; i++) {
	final int index = i;
	new Thread() {
		
		public void run() {
			sThreadLocal.set(index);
			
			try {
				Thread.sleep(1000);
			} catch (Throwable t) {
				t.printStackTrace();
			}
			
			System.out.println(sThreadLocal.get());
		}
		
	}.start();
}
複製代碼

能夠看到,一個ThreadLocal只能用來保存一個對象,若是須要保存多個對象,就須要定義多個ThreadLocal。數組

使用場景

ThreadLocal的做用就是用來保存線程相關的局部對象,當某個對象跟線程有一對一的關係,就能夠使用ThreadLocal進行保存。併發

在Android中,咱們知道一個線程對應一個Looper,所以Looper內部就使用到了ThreadLocal,把線程的Looper對象保存在ThreadLocal中。又好比AnimationHandler類,它內部也定義了一個ThreadLocal對象,用於保存線程的AnimationHandler對象,也就是說,線程的動畫執行最終都是在被同一個AnimationHandler對象處理的,有興趣能夠本身看源碼。框架

咱們還注意到,定義的ThreadLocal雖然在多個線程中被使用,但不會有線程問題,由於每一個線程訪問的都是本身的局部對象。oop

原理分析

  • 總體原理圖性能

    這個Thread就是線程對象,每一個線程只有一個,能夠經過Thread.currentThread()方法拿到,也能夠在new Thread()時拿到,無論什麼方式拿到的都是同個對象,這個對象裏面有一個哈希表,每個Entry的key就是ThreadLocal的弱引用,而value就是set進來的局部對象。優化

    能夠看到這個哈希表是線程惟一的,而裏面的每個Entry,對應一個ThreadLocal對象,好比說我在線程A中用到了3個ThreadLocal,都設置了不一樣的局部對象,那麼這個哈希表就有3個Entry對象。動畫

  • ThreadLocalMap

    上面所說的線程Thread對象中的哈希表,其真實類型就是ThreadLocalMap,它實際上是一個簡化版哈希表,初始容量爲16,固然,只有真的放東西Entry數組纔會初始化,threshold爲容量的2/3,超過了就觸發擴容,擴容也是比較簡單的,就是直接擴大爲原容量的2倍,至於哈希衝突問題,若是衝突就採用線性探測的方法解決。

    這裏還有個問題,就是如何根據key肯定放到哪一個桶裏,能夠看下代碼:

    int i = key.threadLocalHashCode & (len-1);
    複製代碼

    threadLocalHashCode在初始化時被賦值:

    public class ThreadLocal<T> {
      	......
      	private final int threadLocalHashCode = nextHashCode();
    
      	private static final int HASH_INCREMENT = 0x61c88647;
    
      	private static int nextHashCode() {
          	return nextHashCode.getAndAdd(HASH_INCREMENT);
      	}
      	......
      }
    複製代碼

    也就是說,這個threadLocalHashCode,就是0x61c88647的整數倍,而後跟len-1進行與操做,就獲得了桶的下標,至於爲何是0x61c88647這個數值,網上有分析文章,有興趣能夠本身查看。

  • 內存泄漏

    根據上面的總體原理圖,能夠獲得一條引用鏈:

    Thread對象 --> threadLocals --> Entry[] --> WeakReference<ThreadLocal<?>>和value
    複製代碼

    由於Thread對象的生命週期是比較長的,只有當線程退出後,Thread對象纔會被回收,那麼在線程退出前,咱們的ThreadLocal被WeakReference包着,若是外部沒有強引用,在內存不足時gc會自動回收了,但那個value就不會了,須要咱們本身手動去清空引用。

    所以這裏存在一個內存泄漏的問題,若是某個局部對象使用ThreadLocal保存了,而後用完以後沒有清除掉,線程又還沒退出,就可能致使內存泄漏,固然解決的方法也很簡單,調用remove方法就能夠:

    sThreadLocal.remove();
    複製代碼

    另外,並非說這種場景下咱們不調用remove方法,value引用就一直不會被置空,ThreadLocal內部作了優化,在get和set時會主動置空那些key被gc自動回收的value引用,不過通常咱們的ThreadLocal都是定義爲static,這種狀況就不可能會被gc自動回收了。

  • Netty的FastThreadLocal

    若是同個線程用到了多個ThreadLocal,也就是Entry[]會有多個元素,那麼每次在搜索某個ThreadLocal對應在哪一個桶這個過程就會比較耗時,特別是對於服務端併發量很大的狀況下,性能損耗會很明顯,所以FastThreadLocal就作了優化,直接把ThreadLocal的哈希表去掉了,改成用變量index記錄當前ThreadLocal對應的數組下標,也就是空間換時間,實現代碼:FastThreadLocal.java,固然,在Android中對同個線程的ThreadLocal哈希表頻繁操做這種場景可能並很少見。

相關文章
相關標籤/搜索