Java多線程 | 詳解ThreadLocal實現(xiàn)原理

一、ThreadLocal的簡介:

一般情況下,我們創(chuàng)建的變量都是可以給任何線程訪問并修改的,如果我們想讓線程擁有自己的私有本地變量,那我們就可以使用ThreadLocal類是實現(xiàn)這樣的想法。

ThreadLocal類主要解決的是讓每個線程綁定自己的值,可以將ThreadLocal類形象的比喻成存放數(shù)據(jù)的盒子,盒子中可以存儲每個線程的私有數(shù)據(jù)。

我們來看看一個示例代碼了解它的使用:

import java.text.SimpleDateFormat;
import java.util.Random;

/**
 * ThreadLocal示例類
 */
public class ThreadLocalExample {

    //SimpleDateFormat不是線程安全的,所以要為每個線程都創(chuàng)建一個本地副本
    private static final ThreadLocal<SimpleDateFormat> formarter =
            ThreadLocal.withInitial(()->new SimpleDateFormat("yyyyMMdd HHmm"));

    public static void main(String [] args) throws InterruptedException {

        ThreadLocalExample example = new ThreadLocalExample();
        for (int i = 0;i < 10; ++i){
            //創(chuàng)建線程
            Thread t = new Thread(()->{
                System.out.println("Thread Name="+Thread.currentThread().getName()+" deafult Formatter=" +
                        formarter.get().toPattern());
                try {
                    Thread.sleep(new Random().nextInt(1000));
                }catch (Exception e){
                    e.printStackTrace();
                }

                formarter.set(new SimpleDateFormat());

                System.out.println("Thread Name="+Thread.currentThread().getName()+" formatter=" +
                        formarter.get().toPattern());
            },""+i);

            Thread.sleep(new Random().nextInt(1000));
            t.start();
        }

    }

}


我們從結(jié)果中,可以看出,線程0使用了set方法來修改后,變量的值變了,但是對于其他線程在沒有調(diào)用set方法之前,get取到的值都是一開始主線程初始化的默認(rèn)值。

二、ThreadLocal的實現(xiàn)原理

我們先來看看ThreadLocal類里面的一個很重要的靜態(tài)內(nèi)部類ThreadLocalMap

static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        private static final int INITIAL_CAPACITY = 16;
        private Entry[] table;
        private int size = 0;
        .......

這個ThreadLocalMap我們可以將它當(dāng)作一個類似Map的數(shù)據(jù)結(jié)構(gòu),他的key為ThreadLocal對象,值為Object對象。

其實在Thread類中也有兩個ThreadLocalMap的對象,我們看看:

public
class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...

默認(rèn)情況下這兩個變量都是null,只有當(dāng)前線程調(diào)用ThreadLocal類的set或get方法時才創(chuàng)建它們。

實際上的話,ThreadLocal存放的每一個線程的私有變量就是存在當(dāng)前線程對象的threadLocals對象中的,當(dāng)調(diào)用ThreadLocalset方法時,就是先通過Thread.currentThread()來獲取當(dāng)前的線程t,然后再使用getMap(t)方法來獲取當(dāng)前線程對應(yīng)的ThreadLocalMap,然后對這個map進行操作的:

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

我們來看看上面那個通過線程來獲取ThreadLocalMapgetMap()方法的實現(xiàn):

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

這個方法就是直接獲取了線程對象里面的threadLocals,這個在我們剛剛看Thread類的時候已經(jīng)說到了這個對象了。

因此我們可以得出一個結(jié)論就是:最終的變量是放在了當(dāng)前線程的ThreadLocalMap
中,并不是存在ThreadLocal上,ThreadLocal可以理解為只是ThreadLocalMap的封裝,傳遞了變量值。ThrealLocal類中可以通過Thread.currentThread()獲取到當(dāng)前線程對象后,直接通過getMap(Thread t)可以訪問到該線程的ThreadLocalMap對象。

比如我們在同?個線程中聲明了兩個ThreadLocal對象的話,會使?Thread內(nèi)部都是使?僅有那個ThreadLocalMap存放數(shù)據(jù)的,ThreadLocalMap的key就是ThreadLocal對象,value就是ThreadLocal對象調(diào)?set?法設(shè)置的值。


三、關(guān)于ThreadLocal的內(nèi)存泄露的問題

我們來看看ThreadLocalMap中的鍵值對對象Entry

      static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

ThreadLocalMap中使用的key為ThreadLocal的弱引用,而value是強引用。所以,如果ThreadLocal沒有被外部強引用的情況下,在垃圾回收的時候,key會被清理掉,而value不會被清理掉。這樣?來,ThreadLocalMap中就會出現(xiàn)key為null的Entry。假如我們不做任何措施的話,value永遠(yuǎn)無法被GC回收,這個時候就可能會產(chǎn)?內(nèi)存泄露。ThreadLocalMap實現(xiàn)中已經(jīng)考慮了這種情況,在調(diào)用set()、get()、remove()方法的時候,會清理掉key為null的記錄。使用完ThreadLocal方法后最好手動調(diào)用remove()方法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容