網上有很多關於ThreadLocal的文章,大部分都提到了多線程之間共享資源的問題。其實ThreadLocal和多線程之間一點關係都沒有。如果有,我怕是它的名字改成ThreadShare是不是更合適呢?開個玩笑。從其名稱ThreadLocal,我們就可以看出他應該是隸屬於線程內部的資源。接下來就詳細說說吧。
我們以前在處理一個Request請求,穿透多個業務方法的時候,如果要共享數據,一般都會搞個Context上下文對象,在多個方法之間進行傳遞,這樣可以解決多個方法之間的數據共享問題。這是很不錯的一種方案,而且這種方案在實際使用的時候效果也非常不錯,因爲Context內部我們想放什麼,就放什麼,想怎麼放就怎麼放,非常的自由無界。但是這種自由帶來的問題在小流量的請求中是不會有問題的,但是在大流量的請求中,則存在不小的問題,主要在:
1. Context對象,每個請求進來,都會new一個,大流量下,瞬間暴增,由於空間申請操作勢必引發頻繁的young GC, 業務壓力大的時候,full GC也是不可避免的。
2. Context對象,在一個請求終結之後,需要手動釋放。
3. Context對象,存在被請求內部的多線程共享訪問的情形。有線程安全性問題。
上面三個問題,是我隨便列舉的,但是在實際使用中,可能還有更多。但是如果Context的生產和銷燬如果控制的足夠好的話,上面的問題也不是什麼問題。
既然Context對象的控制稍顯麻煩,那麼JDK有沒有提供什麼現成的類庫供我們使用呢? 答案是肯定的,這個對象就是ThreadLocal對象。
說道ThreadLocal對象,我更認爲他是在當前請求的上游和下游之間進行數據共享的。那麼按照之前的例子說來,如果一個請求穿越多個業務方法,其實數據的共享可以利用ThreadLocal來進行,這樣就無需專門定義一個Context。
首先來看看其內部get實現機制:
/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } protected T initialValue() { return null; }
從上面源碼可以看出,get操作會從當前Thread上附加的map中進行數據獲取。
再來看看set方法:
/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ 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 getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
從上面源碼看出,首先他會取出當前Thread,然後會將map設置到當前Thread上,用戶可以在這個map上進行數據增刪改查操作。非常巧妙。所以從這裏可以看出,一個ThreadLocal對象,即便穿插在多個線程之間,也不會造成資源共享問題,因爲他會爲每個線程都設置map對象,這也從根本上避免了線程安全問題。
最後,因爲ThreadLocal內部的對象爲WeakReference,所以不用進行手動釋放,只需要保證一個請求結束,對其內部的引用釋放掉就行了,然後自動會被JVM優先處理掉,根本無需擔心內存泄露問題。