Java線程封閉

把對象封裝到一個線程裏,只有一個線程能夠看到該對象,那麼就算這個對象不是線程安全的,也不會出現任何線程問題,由於它只能在一個線程中被訪問。
  • Ad-hoc線程封閉:程序控制實現,很是脆弱,最糟糕,忽略。
  • 堆棧封閉:簡單的說就是局部變量,無併發問題。多線程訪問同一個方法時,方法中的局部變量會被拷貝一份到線程棧中。方法的局部變量不是被多線程共享的,不會出現線程安全問題,能用局部變量就不要用全局變量,全局變量容易發生併發問題,注意全局變量不是全局常量。
  • ThreadLocal線程封閉:Java中提供一個ThreadLocal類來實現線程封閉,這個類使線程中的某個值與保存值的對象關聯起來

ThreadLocal

ThreadLocal類提供的方法

image-20210105141906919

核心的五個操做:建立,建立並賦初始值,賦值,取值,刪除
  • 建立:
private final static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>;
  • 建立並賦初始值
private final static ThreadLocal<String> threadLocal=new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "入門小站";
        }
};
  • 賦值
threadLocal.set("入門小站");
  • 取值
threadLocal.get();
  • 刪除
threadLocal.remove();

實現原理

首先 ThreadLocal是一個泛型類,保證能夠接受任何類型的對象。

一個線程內能夠存在多個ThreadLocal,ThreadLocal內部維護了一個Map,這個Map不是HashMap,而是ThreadLocal實現的一個ThreadLocalMap的靜態內部類。咱們使用的get(),set()方法實際上是調用了這個ThreadLocalMap類對應的get(),set()安全

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}
調用 ThreadLocal的set方法時,先獲取當前的線程 Thread t = Thread.currentThread();,而後獲取當前線程維護的 ThreadLocalMap。若是 ThreadLocalMap不存在則初始化。

ThreadLocalMapmap.set(this, value);第一個參數是this,this指的是當前的ThreadLocal,就是上面代碼裏面的threadLocal變量。微信

最終的變量是放在當前線程的ThreadLocalMap中,並非存在ThreadLocal上,ThreadLocal能夠理解成傳遞關係的。多線程

內存泄漏問題

image-20210105172630930

ThreadLocalMap中使用的 keyThreadLocal的弱引用,弱引用的特色是,若是這個對象只存在弱引用,那麼在下一次垃圾回收的時候必然會被清理掉。

因此ThreadLocal沒有被強引用的狀況下,在垃圾回收的時候會被清理掉,可是value倒是強引用,不會被清理,這樣的話就出出現keynullvalue併發

ThreadLocalMap實現中已經考慮了這個狀況,在調用set,get,remove方法的時候會清理掉keynull的記錄。若是出現了內存泄漏,那就是說在keynull後,沒有手動調用remove方法,而且以後也再也不調用set,get,remove方法。app

內存泄漏解決方案

將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命週期就更長,因爲一直存在ThreadLocal的強引用,因此ThreadLocal也就不會被回收,也就能保證任什麼時候候都能根據ThreadLocal的弱引用訪問到Entry的value值,而後remove它,防止內存泄露。ide

如何保證兩個同時實例化的ThreadLocal對象有不一樣的threadLocalHashCode屬性

在ThreadLocal類中,還包含了一個static修飾的AtomicInteger([əˈtɒmɪk]提供原子操做的Integer類)成員變量(即類變量)和一個static final 修飾的常量(做爲兩個相鄰nextHashCode的差值)。因爲nextHashCode是類變量,因此每一次調用ThreadLocal類均可以保證nextHashCode被更新到新的值,而且下一次調用ThreadLocal類這個被更新的值仍然可用,同時AtomicInteger保證了nextHashCode自增的原子性。

ThreadLocal應用

場景一

Web項目公共參數從controller層傳遞到service層,再從service層傳遞到mapper層,或者從service層傳遞到其餘的工具類當中。爲了不參數複雜的傳遞,在controller中將已經封裝好的參數放入ThreadLocal中,在其餘層調用時直接經過ThreadLocal對象獲取。在方法結束時,定義攔截器(HandlerInterceptorAdapter)(或者Filter)進行ThreadLocal的remove方法。

場景二

在須要登陸的系統中用戶信息經常存在 Sessiontoken。好比咱們要從 Session中獲取用戶信息須要在接口參數中加上HttpServletRequest對象,而後調用 getSession方法,且每個須要用戶信息的接口都要加上這個參數,才能獲取Session,比較麻煩。

這個時候咱們就能夠用ThreadLocal,在攔截器(HandlerInterceptorAdapter)(或者Filter)中解析獲取用戶信息,而後保存到ThreadLocal,業務邏輯直接在ThreadLocal中獲取就能夠了。工具

【關注微信公衆號:【入門小站】解鎖更多知識點】
imagethis

相關文章
相關標籤/搜索