我們知道,進程是OS分配資源的最小單位,而線程是執(zhí)行操作的最小單位并不享有資源。ThreadLocal實現(xiàn)了線程數(shù)據(jù)變量的本地存儲,每個線程都存儲自己的變量,所有的線程都使用同樣的ThreadLocal<T>對象來存取變量,但是每個線程在存取時看到的變量值是不同的,不會影響到其他線程的變量,并且值可以為null。
整個實現(xiàn)架構圖如下:

一個線程可以持有多個ThreadLocal對象,每個ThreadLocal實例其實很輕量級,只保存hashCounter分配給它的hash值和自身的一個弱引用。存取時只要將values里的obj數(shù)組復制當前方法的局部變量中操作就可以了。
原來的JDK中,table是用map實現(xiàn)的;在1.7源碼中使用了object數(shù)組來存放<ThreadLocal,T>,如圖間隔存放了kv;這樣做避開了線程并發(fā)的鎖操作,大大加快了存取的速度。另外值得提一點的是,hashCounter每次分配給ThreadLocal對象的hash值都是偶數(shù),這樣取得的index位置來存放ThreadLocal對象,index+1位置存放變量值,十分巧妙。
ThreadLocal類結構如下,我們接下來依次分析這些方法:

介紹完了整個架構,我們先不去看values這個靜態(tài)內部類,其實它維護了ThreadLocal到變量值的映射,一個hashtable而已,回頭再來看它。
先來看一眼完成當前線程變量值的get方法:
public T get() { // Optimized for the fast path. Thread currentThread = Thread.currentThread();//獲取到當前線程實例 Values values = values(currentThread);//獲取到當前線程對應的values實例 if (values != null) { Object[] table = values.table; int index = hash & values.mask; if (this.reference == table[index]) { return (T) table[index + 1]; } } else { values = initializeValues(currentThread);//如果當前線程對應的values為空,就新建一個 } return (T) values.getAfterMiss(this); }方法中,根據(jù)當前線程實例獲取到values,先來看看如果values為空會如何?
如果values為空則對其初始化,調用initializeValues方法:
/** * Creates Values instance for this thread and variable type. */ Values initializeValues(Thread current) { return current.localValues = new Values();//new一個values實例 }我們來看一下Values的構造方法:
Values() { initializeTable(INITIAL_SIZE);//INITIAL_SIZE為16 this.size = 0;//table中存儲的鍵值對entry數(shù)目 this.tombstones = 0;//廢棄的entry數(shù)目 }通過initializeTable方法來創(chuàng)建一個object數(shù)組,容量為32,mask值為0x1F。
private void initializeTable(int capacity) { this.table = new Object[capacity * 2];//通過給定的初始化容量創(chuàng)建table,一個obj數(shù)組 this.mask = table.length - 1;//之前capacity規(guī)定必須為2的冪,這里length默認為31, this.clean = 0; this.maximumLoad = capacity * 2 / 3; // 2/3 最大負載因子 }我們重新回到get方法中,方法最后返回getAfterMiss(this),該方法將當前ThreadLocal傳入,并返回initialValue()定義的值,這個方法是可以自定義重寫的。
如果values不為空,我們將副本table復制到當前方法變量中進行操作,由于每個ThreadLocal對象都有固定的hash值,所以不存在線程并發(fā)的問題。
ThreadLocal中其他的操作方法也是這樣。操作完成后,我們需要與Values中的數(shù)組交互,這里就調用了put方法:
/** * Sets entry for given ThreadLocal to given value, creating an * entry if necessary. */ void put(ThreadLocal<?> key, Object value) { cleanUp();//先清理了廢棄的元素 // Keep track of first tombstone. That's where we want to go back // and add an entry if necessary. int firstTombstone = -1; for (int index = key.hash & mask;; index = next(index)) { Object k = table[index]; if (k == key.reference) { // Replace existing entry. table[index + 1] = value; return; } if (k == null) { if (firstTombstone == -1) { // Fill in null slot. table[index] = key.reference; table[index + 1] = value; size++; return; } // Go back and replace first tombstone. table[firstTombstone] = key.reference; table[firstTombstone + 1] = value; tombstones--; size++; return; } // Remember first tombstone. if (firstTombstone == -1 && k == TOMBSTONE) { firstTombstone = index; } } }每次操作object數(shù)組,都要先清理一下廢棄的元素。然后再進行元素存放。
總結
ThreadLocal與values的組合設計實現(xiàn)了多個線程存儲本地變量而又互不干擾的功能,更令人叫絕的是通過固定hash值分配的方式,避開了鎖操作。關于Values內部的object數(shù)組的維護比較復雜,以后有機會再來研究補充。
新聞熱點
疑難解答