java ThreadLocal執行緒設定私有變數底層原始碼分析
前面也聽說了ThreadLocal來實現高併發,以前都是用鎖來實現,看了挺多資料的,發現其實還是區別挺大的(感覺嚴格來說ThreadLocal並不算高併發的解決方案),現在總結一下吧。
高併發中會出現的問題就是執行緒安全問題,可以說是多個執行緒對共享資源訪問如何處理的問題,處理不當會的話,會出現結果和預期會完全不同。
一般情況下,多個執行緒訪問一個變數都是公用他們的值,不過有時候雖然也是訪問共享變數,不過每個執行緒卻需要自己的私有變數。這個時候ThreadLocal就有用武之地了。下面是個ThreadLocal的簡單例項:
public class ThreadLocalExample { public static void main(String[] args){ //建立一個ThreadLocal物件 ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); //設定主執行緒私有變數值 threadLocal.set(100); //建立一個新執行緒 new Thread(new Runnable(){ public void run(){ //使用共享變數,設定執行緒私有變數 threadLocal.set(50); System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); } }).start(); System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); } }
輸出結果:
main:100 Thread-0:50
很神奇,對多個資源之間的共享,又不想他們之間相互影響,所以使用這個是挺不錯的。具體應用,spring中應用我記得挺多的,連線資料庫的每個連線,還有session的儲存。
思考了一下,要我實現的話就用個map來儲存,因為這個其實就是鍵值對,只不過鍵是執行緒唯一標識,值就是對應的私有變數。
具體看了原始碼發現差不多,不過使用內部自己實現的一個ThreadLocalMap類,內部還一個Entry類而且Entry類繼承weakRefrence(說實話第一次遇到弱應用,以前只是在jvm那本書學習了下),具體方法如下:
先看下他的set方法吧
public void set(T value) { Thread t = Thread.currentThread(); //獲得所有執行緒共享的ThreadLocalMap物件 ThreadLocalMap map = getMap(t); //物件已經存在就直接插入鍵值對 //不存在就建立然後再插入 if (map != null) map.set(this, value); else createMap(t, value); }
getMap方法的話一個獲得所有執行緒共享的ThreadLocalMap物件如下:
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
然後進入Thread類進去找一下這個容器,找到下面:
ThreadLocal.ThreadLocalMap threadLocals = null;
然後建立:
void createMap(Thread t, T firstValue) {
//建立ThreadLocalMap物件賦給threadLocals t.threadLocals = new ThreadLocalMap(this, firstValue); }
至此,ThreadLocal的基本原理就已經很清晰了:各執行緒對共享的ThreadLocal例項進行操作,實際上是以該例項為鍵對內部持有的ThreadLocalMap物件進行操作。
還有get()方法的話就是利用設定的鍵進行獲取,remove()方法也是,其實和Hashmap差不多不過解決衝突使用的拉鍊法(對了,下次寫一篇HashHap的還有ConcurrentHashMap的話,頗有研究)。這裡有個問題就是因為這個ThreadLocalMap是靜態的所以在方法區中(jdk8之後為元資料區),不進行回收的話會造成記憶體洩漏,而且可能會出現記憶體溢位,所以使用後記得remove();
基本上其實可以了,不過好奇ThreadLocalMap怎麼實現的可以接著往下看,我也好奇,所以也偷偷看了,嘿嘿嘿
那就來分析一下這個ThreadLocalMap這個內部類吧。
ThreadLocalMap屬於一個自定義的map,是一個帶有hash功能的靜態內部類,其實和java.util包下提供的Map類並沒有關係。內部有一個靜態的Entry類,下面具體分析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,用main方法引用的欄位作為entry中的key。當entry.get() == null的時候,意味著鍵將不再被引用。
注意看到一個super(k),說明呼叫父類的構造,去看看
Reference(T referent) { this(referent, null); } Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; }
就上面這個其他沒了,看了半天有點沒看懂,然後去學了四種引用回來終於看懂,由於篇幅過多,在結尾我給出兩篇別人的部落格,可以去看完了,再回來,多學點哈哈哈。
再看了下發現這個內部類好多,但是其實就是map的一種實現,上面也講了set方法那就簡單提一下ThreadLocalMap的set()方法相關的吧,程式碼下面:
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物件陣列拿到來 ThreadLocal.ThreadLocalMap.Entry[] tab = table; //長度也拿到來 int len = tab.length; //通過拿到key的hashcode值,進去發現神奇的一幕這裡利用通過累加這個值0x61c88647來作為hashcode, // 這裡提一下往下走發現因為要公用這個屬性,多個例項訪問會有問題 // 所以使用了AtomicInteger原子操作來寫值 //並且與總長度-1做與運算就是取模,因為擴容都是2的n次方所以這樣直接取模就行,速度快 int i = key.threadLocalHashCode & (len-1); //定位到對應的陣列位置,進行衝突判斷之類的處理 for (ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { //這裡是衝突遍歷 //這裡裡面就拿對應tabel下對應位置的當前引用 ThreadLocal<?> k = e.get(); //判斷是不是對應的鍵,是的話就覆蓋 if (k == key) { e.value = value; return; } //沒有的話就生成Entry代替掉 if (k == null) { replaceStaleEntry(key, value, i); return; } } //這裡就直接插入了 tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value); //長度加1 int sz = ++size; //判斷是否做擴容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
裡面其實挺複雜的,具體的話就是正常是使用開放定址法處理,這裡使用累加一個定值解決的衝突,因為多個例項共用,特殊處理,厲害厲害。
//threadLocalHashCode程式碼也貼在這裡吧,有興趣可以直接去看 private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } private final int threadLocalHashCode = nextHashCode();
總結
看完原始碼之後神清氣爽,學到了很多啦。以前對java引用只是知道四個引用和對應的相應簡單概念,為了看懂這個Entry,去學習了weakReference原始碼,看了別人的關於四個引用的部落格寫的真好,偷偷學習了下,並且知道怎麼使用了。劃重點會用了!!!當然對於ThreadLocal也會用了,而且好像可以手寫一個簡單的版本哎,可以動手試試。
關於四種引用部落格,寫的真的很棒。
https://blog.csdn.net/swebin/article/details/78571933
https://blog.csdn.net/hacker_zhidian/article/details/8304