[021]java中ThreadLocal原理及其使用

背景

我們都知道ThreadLocal可以解決線程安全問題,它會把共享變量copy一份副本到線程空間中。那么它是怎么做到的,我們又應該如何使用它呢?

Thread Local 是什么

本地變量副本:

它不是一個線程,而是線程的一個本地化對象。當工作于多線程中的對象使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程分配一個獨立的變量副本。所以每一個線程都可以獨立地改變自己的副本,而不會影響其他線程所對應的副本。從線程的角度看,這個變量就像是線程的本地變量,這也是類名中“Local”所要表達的意思。

Thread Local背后的實現機制-原理

1.每個線程都有一個ThreadLocalMap變量,所以可以想到每個線程獨立的變量都是通過ThreadLocalMap數據結構來維護的。

//Thread.java 中
public class Thread implements Runnable {
    ...
    ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocal{
    /**
    * 1.獲取本地線程 thread.currentThread();
    * 2.取得本地線程的threadLocalMap,然后往這個map里塞值。
    */
    public void set(T value) {
        Thread t = Thread.currentThread(); //從這里可以看出代碼執行這里,就會往當前線程設置變量。
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // ThreadLocalMap(ThreadLocal,Object),以ThreadLocal作為key,object作為value。
            map.set(this, value);
        else
            createMap(t, value);
    }

     /**
     *
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
}

如何使用Thread Local

了解了ThreadLocal的使用原理,我們先看ThreadLocal類有哪些方法。
initValue()方法
set()方法
get()方法

ThreadLocal{
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//this為ThreadLocal對象
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
}

我們定義如下threadLocal變量。
private ThreadLocal myThreadLocal1 = new ThreadLocal<String>();
private ThreadLocal myThreadLocal2 = new ThreadLocal<String>();
在實際使用過程中
myThreadLocal1.get(),獲取的是當前線程中threadLocalMap 里myThreadLocal1為key對應的值。
當調用myThreadLocal1.set("abc"),自動的把abc設置到當前Thread.ThreadLocalMap()中去了,參考ThreadLocal類型set()方法。

注意我們在Thread中添加的ThreadLocal.ThreadLocalMap 變量在線程結束的時候必須釋放掉,請查看Thread類exits方法:

 /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

threadLocal 與thread的同步鎖比較

threadLocal 是為每個線程維護一份本地的變量,所以是“空間換時間”的方式。
thread同步鎖 訪問串行化、對象共享化,是“時間換空間”的方式。

如何正確使用threadLocal

由于threadLocal就是解決多個線程之間互相干擾的情況,每個線程都有他們自己的threadLocalMap數據結構。
所以各個線程的threadLocalMap不能指向同一個引用,即每個線程需要肚子引用記錄自己的對象threadLocalMap.set(this,new Object())這樣線程之間才不會干擾。

/**
 * 由于是thread中有一個threadLocal.threadlocalMap引用,所以
 * @author shawn
 *
 */
public class SafeThreadLocalThread  implements Runnable{
    
    private ThreadLocal myThreadLocal = new ThreadLocal<Number>();
    
    public static  int i = 0;
    
    @Override
    public void run() {
        Number number = new Number();//每個線程需要使用自己的對象。
        number.setNo(i++);
        
        //將值存儲到threadLocal中
        myThreadLocal.set(number);
        
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("number.getNo()" + number.getNo());
    }
    
    public static void main(String[] args) {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            newCachedThreadPool.execute(new SafeThreadLocalThread());
        }
    }
    
    /**
     * 每個number 各不影響因為他是在各個線程里單獨new的。
     * number.getNo()2
     *number.getNo()3
     *number.getNo()1
     *number.getNo()4
     *number.getNo()0
     */
}

詳細代碼請查看:https://github.com/shawnxjf1/J2seCodeExample

不同級別的緩存

1.threadLocal相當于線程級別的緩存,每個線程綁定一個map容器在這個容器里存儲該線程里的數據。
2.而我們在代碼中經常用到static 變量的緩存,比如如下代碼:

class demo {
 public static Map<String,Object> demoCache= new Hashmap();//定義jvm級別的緩存
}

3.對于tomcat servlet container,j2ee container 每個容器都有自己的classloader,比如每個tomcat每個應用都有自己的appclassLoader只加載自己的class.
比如如下應用app1,app2可能需要緩存的數據是不一樣的,我們希望操作app1 的demoCache時候不影響應用app2的。
此時我們需要的緩存級別就是按容器級別(每個容器的classloader不一樣),最常用到的容器級別的工具是:

org.apache.commons.beanutils.ContextClassLoaderLocal{
    private final Map<ClassLoader, T> valueByClassLoader = new WeakHashMap<ClassLoader, T>();
}

在apache beanUtils組建注冊convert的時候都是放在per(thread) context classloader級別的緩存里。
當然如果上述代碼運行在非容器(意味著只有一個classLoader),其效果就相當于static 變量緩存了。
相關代碼見參考列 [beanutils注冊convert]。

寫完后想法

以前使用ThreadLocal 都是從網上摘抄代碼片段,但是當我弄明白ThreadLocal原理自己就能夠靈活使用ThreadLocal代碼來。弄明白了原理后你就是從腦子出發來表達和實現ThreadLocal信息,而如果你只是copy網上代碼說明信息是沒有經過腦子的,當然我們不要求所有的技術都弄得很深但是你使用它的時候一定要知道它與其他模塊或代碼對接的原理。

參考

參考1:beanUtils 注冊convert

//ContextClassLoaderLocal 類似于threadLocal
BeanUtilsBean{
      private static final ContextClassLoaderLocal<BeanUtilsBean>
            BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal<BeanUtilsBean>() {
                        // Creates the default instance used when the context classloader is unavailable
                        @Override
                        protected BeanUtilsBean initialValue() {
                            return new BeanUtilsBean();
                        }
                    };

    /**
     * 獲取當前classloader的BeanutilsBean
     * Gets the instance which provides the functionality for {@link BeanUtils}.
     * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
     * This mechanism provides isolation for web apps deployed in the same container.
     *
     * @return The (pseudo-singleton) BeanUtils bean instance
     */
    public static BeanUtilsBean getInstance() {
        return BEANS_BY_CLASSLOADER.get();
    }

    /**
     * Sets the instance which provides the functionality for {@link BeanUtils}.
     * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
     * This mechanism provides isolation for web apps deployed in the same container.
     *
     * @param newInstance The (pseudo-singleton) BeanUtils bean instance
     */
    public static void setInstance(final BeanUtilsBean newInstance) {
        BEANS_BY_CLASSLOADER.set(newInstance);
    }
}

ConvertUtilsBean{
   protected static ConvertUtilsBean getInstance() {
        return BeanUtilsBean.getInstance().getConvertUtils();
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 前言 ThreadLocal很多同學都搞不懂是什么東西,可以用來干嘛。但面試時卻又經常問到,所以這次我和大家一起學...
    liangzzz閱讀 12,505評論 14 228
  • Android Handler機制系列文章整體內容如下: Android Handler機制1之ThreadAnd...
    隔壁老李頭閱讀 7,678評論 4 30
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,368評論 11 349
  • 中國文化中的“八卦圖”讓我覺得十分神奇,這世間萬物似乎都能用一張“八卦圖”來解釋。 男人和女人的結合、童年...
    2017建軍節閱讀 515評論 0 0
  • 等娃放學,無聊中亂拍,拍下斑馬線,P上可愛的小動物,也蠻好玩啊。 日常常見的景物,加點點小創意,馬上變不同。
    Sarahyaoyao閱讀 273評論 0 1