Java-ThreadLocal

定義

線程本地變量褐澎,也有些地方叫做線程本地存儲岩臣,其實意思差不多脚牍。ThreadLocal可以讓每個線程擁有一個屬于自己的變量的副本向臀,不會和其他線程的變量副本沖突,實現(xiàn)了線程的數(shù)據(jù)隔離诸狭。

使用示例

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    static class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            threadLocal.set("son");
            System.out.println(threadLocal.get());  //son
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        threadLocal.set("main");
        System.out.println(threadLocal.get());  //main
    }
}

通過上面的代碼我們發(fā)現(xiàn)券膀,當(dāng)我我們使用同一個ThreadLocal對象在不同線程中set不同的值,會打印出不同的值驯遇,這是怎么做到的呢芹彬?上面定義中提到每個線程擁有一個屬于自己的變量的副本,這就能解通為什么我們可以獲取到不同的值叉庐。

既然每個線程有一個自己的變量副本舒帮,那我們自己是不是也可以實現(xiàn)呢?答案是肯定的陡叠,下面我們自己實現(xiàn)一個類似的功能玩郊。

//自定義實現(xiàn)的ThreadLocal
public class MyThreadLocal<T> {
    private Map<Thread, T> hashMap = new HashMap();
    
    //多線程下保證原子性
    public synchronized void set(T t) {
        synchronized (MyThreadLocal.this) {
            hashMap.put(Thread.currentThread(), t);
        }
    }

    public synchronized T get() {
        return hashMap.get(Thread.currentThread());
    }
}

public class ThreadLocalTest {
    //使用我們自己定義的ThreadLocal
    private static MyThreadLocal<String> threadLocal = new MyThreadLocal<>();

    static class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            threadLocal.set("son");
            System.out.println(threadLocal.get());  //son
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        threadLocal.set("main");
        System.out.println(threadLocal.get());  //main
    }
}

我們自己實現(xiàn)了一個MyThreadLocal對象,創(chuàng)建了一個Key是Thread的Map對象存儲對應(yīng)線程的數(shù)據(jù)枉阵,也實現(xiàn)了ThreadLocal的功能译红,下面我們看看系統(tǒng)的ThreadLocal是怎么做的。

源碼分析

我們從set方法開始分析兴溜。

    public void set(T value) {
        // 獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        //獲取ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
        if (map != null) //不為空直接調(diào)用ThreadLocalMap的set方法
            map.set(this, value);  
        else
            createMap(t, value);  //為空創(chuàng)建ThreadLocalMap 并將數(shù)據(jù)存儲起來
    }

然后跟進getMap方法

ThreadLocalMap getMap(Thread t) {
        //這里調(diào)用到了Thread中侦厚,也就是說ThreadLocalMap對象是從Thread中獲取的
        return t.threadLocals;
    }

繼續(xù)看看Thread的threadLocals對象

    //Thread中定義的threadLocals屬性
    ThreadLocal.ThreadLocalMap threadLocals = null;

繼續(xù)看看ThreadLocalMap的set方法

 private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //如果存在key則更新值
                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //沒有對應(yīng)的key則添加到數(shù)組中
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

上面我們看到反璃,存值的時候?qū)hreadLocal和Object(我們存儲的數(shù)據(jù))封裝到了一個Entry對象中,下面我們看下這個Entry

//ThreadLocalMap中的內(nèi)部類 存儲了ThreadLocal和我們保存的值
static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            //ThreadLocal作為key
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

這里我們可以總結(jié)下大致的流程
1假夺、ThreadLocal調(diào)用set方法淮蜈,然后獲取當(dāng)前調(diào)用的Thread
2、根據(jù)Thread獲取threadLocals屬性(ThreadLocalMap)
3已卷、調(diào)用ThreadLocalMap的set方法
4梧田、將ThreadLocal和我們保存的值封裝成Entry對象,添加到數(shù)組(table)中

我們知道了存儲的大致流程侧蘸,獲取其實也是一樣的裁眯,都是對應(yīng)的對象調(diào)用流程,我們就不介紹了讳癌。

我們上面也實現(xiàn)了自己的MyThreadLocal對象穿稳,也完成了線程副本數(shù)據(jù)的存儲,我們思考下晌坤,這兩種方式有什么區(qū)別呢逢艘?系統(tǒng)為什么要做這么多操作步驟去實現(xiàn)線程副本數(shù)據(jù)呢?

我們自己的實現(xiàn)方式是創(chuàng)建了一個Map去存儲骤菠,當(dāng)多線程情況下它改,為了保證原子性,我們加了鎖商乎。
如果當(dāng)前有很多線程要獲取數(shù)據(jù)央拖,那么這些線程都會去爭奪這個map,沒有拿到的就會阻塞鹉戚,這樣性能上肯定是會有影響的鲜戒。

系統(tǒng)的實現(xiàn)方式則是,沒個線程都有自己的map抹凳,不存在并發(fā)的問題遏餐,自己用自己的,效率無疑是要高的却桶,所以系統(tǒng)的實現(xiàn)還是有一定道理的境输。

這就跟打籃球是一樣的,大家都搶一個球颖系,和每個人都有一個球嗅剖,效果肯定是不一樣的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嘁扼,一起剝皮案震驚了整個濱河市信粮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌趁啸,老刑警劉巖强缘,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件督惰,死亡現(xiàn)場離奇詭異,居然都是意外死亡旅掂,警方通過查閱死者的電腦和手機赏胚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來商虐,“玉大人觉阅,你說我怎么就攤上這事∶爻担” “怎么了典勇?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長叮趴。 經(jīng)常有香客問我割笙,道長,這世上最難降的妖魔是什么眯亦? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任伤溉,我火速辦了婚禮,結(jié)果婚禮上搔驼,老公的妹妹穿的比我還像新娘谈火。我一直安慰自己,他們只是感情好舌涨,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扔字,像睡著了一般囊嘉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上革为,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天扭粱,我揣著相機與錄音,去河邊找鬼震檩。 笑死琢蛤,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抛虏。 我是一名探鬼主播博其,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼迂猴!你這毒婦竟也來了慕淡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤沸毁,失蹤者是張志新(化名)和其女友劉穎峰髓,沒想到半個月后傻寂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡携兵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年疾掰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徐紧。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡静檬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浪汪,到底是詐尸還是另有隱情巴柿,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布死遭,位于F島的核電站广恢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏呀潭。R本人自食惡果不足惜钉迷,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钠署。 院中可真熱鬧糠聪,春花似錦、人聲如沸谐鼎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狸棍。三九已至身害,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間草戈,已是汗流浹背塌鸯。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留唐片,地道東北人丙猬。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像费韭,于是被迫代替她去往敵國和親茧球。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351