ThreadLocal之我所見

網上有很多關於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優先處理掉,根本無需擔心內存泄露問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章