前面也據說了ThreadLocal來實現高併發,之前都是用鎖來實現,看了挺多資料的,發現其實仍是區別挺大的(感受嚴格來講ThreadLocal並不算高併發的解決方案),如今總結一下吧。java
高併發中會出現的問題就是線程安全問題,能夠說是多個線程對共享資源訪問如何處理的問題,處理不當會的話,會出現結果和預期會徹底不一樣。web
通常狀況下,多個線程訪問一個變量都是公用他們的值,不過有時候雖然也是訪問共享變量,不過每一個線程卻須要本身的私有變量。這個時候ThreadLocal就有用武之地了。下面是個ThreadLocal的簡單實例:spring
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那本書學習了下),具體方法以下:session
先看下他的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對象以下:jvm
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也會用了,並且好像能夠手寫一個簡單的版本哎,能夠動手試試。
關於四種引用博客,寫的真的很棒。