ThreadLocal源碼解讀

每個線程都有一個ThreadLocal線程本地變量,各個線程本地變量互不干擾。TreadLocalMap類型的變量(該類是一個輕量級的Map),可以調(diào)用set(),get()方法存取值,可以貫穿整個線程生命周期。鍵為當(dāng)前線程的id,值為Object類型。
作用:提供一個線程內(nèi)公共變量,減少同一個線程內(nèi)多個函數(shù)或者組件之間一些公共變量的傳遞的復(fù)雜度,讓線程的本地變量進(jìn)行隔離。

使用ThreadLocal示例:
創(chuàng)建ThreadLocal,定義threadlocal存儲值的類型,并且可以進(jìn)行初始化。通過threadLocal.get()獲取值,threadLocal.set(s)設(shè)置值。

public class ThreadLocalUse {

    //創(chuàng)建threadLocal時并初始化
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Nullable
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    public static void test() {
        Thread[] threads = new Thread[3];
        for (int i=0;i<threads.length;i++) {
            threads[i] = new Thread(new TestThread(i), "thread" + i);
        }
        for (int i=0;i<threads.length;i++) {
            threads[i].start();
        }
    }

    public static class TestThread implements Runnable {
        int id;

        public TestThread(int id) {
            this.id = id;
        }

        @Override
        public void run() {
            Integer s = threadLocal.get();
            s = s + id;
            threadLocal.set(s);
            Log.i(TAG, Thread.currentThread().getName() + "  threadLocal -> value: " + threadLocal.get());
        }
    }
}

調(diào)用ThreadLocalUse.test()啟動線程,打?。?/p>

thread0  threadLocal -> value: 1
thread1  threadLocal -> value: 2
thread2  threadLocal -> value: 3

3個threadLocal存儲初始值都是1,加上各自的id,分別為0,1,2,然后就打印出了1,2,3的各個值。3個ThreadLocal保存的值各不影響,各有3份副本。

不適用Threadlocal來存儲的示例:

public class UseThreadLocal {
    
    static Integer threadLocal = new Integer(1);

    public void start() {
        Thread[] threads = new Thread[3];
        for (int i=0;i<threads.length;i++) {
            threads[i] = new Thread(new TestThread(i));
        }
        for (int i=0;i<threads.length;i++) {
            threads[i].start();
        }
    }

    public static class TestThread implements Runnable {
        int id;

        public TestThread(int id) {
            this.id = id;
        }

        @Override
        public void run() {
            threadLocal = threadLocal + id;
            System.out.println(Thread.currentThread().getName() + "  threadLocal -> value: " + threadLocal);
        }
    }

}

打印的結(jié)果:
本來該打印1,2,3的,結(jié)果打印了1,2,4,數(shù)據(jù)就錯亂了。


源碼:

進(jìn)入ThreadLocal的get方法:

    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();
    }

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

獲取當(dāng)前線程然后調(diào)用了getMap(Thread t) 方法,調(diào)用了當(dāng)前線程的threadLocals。進(jìn)入之后,得到了ThreadLocalMap類型的成員變量,每個線程都有一個自己的ThreadLocalMap。

ThreadLocalMap類部分代碼:

    static class ThreadLocalMap {
        private static final int INITIAL_CAPACITY = 16;
        private ThreadLocal.ThreadLocalMap.Entry[] table;
        private int size;
        private int threshold;

        private void setThreshold(int var1) {
            this.threshold = var1 * 2 / 3;
        }

        private static int nextIndex(int var0, int var1) {
            return var0 + 1 < var1 ? var0 + 1 : 0;
        }

        private static int prevIndex(int var0, int var1) {
            return var0 - 1 >= 0 ? var0 - 1 : var1 - 1;
        }

        ThreadLocalMap(ThreadLocal<?> var1, Object var2) {
            this.size = 0;
            this.table = new ThreadLocal.ThreadLocalMap.Entry[16];
            int var3 = var1.threadLocalHashCode & 15;
            this.table[var3] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
            this.size = 1;
            this.setThreshold(16);
        }
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> var1, Object var2) {
                super(var1);
                this.value = var2;
            }
        }
}

它有一個靜態(tài)內(nèi)部類Entry,保存一個value,數(shù)據(jù)存儲在Entry類型的數(shù)組中,獲取entry的value需要傳入threadLocal來作為key進(jìn)行獲取。

那么根據(jù)源碼畫出示意圖:


創(chuàng)建一個靜態(tài)的ThreadLocal變量,然后創(chuàng)建多個線程,通過threadLocal變量存和取數(shù)據(jù)。當(dāng)調(diào)用threadLocal.set時,判斷當(dāng)前線程的threadLocalMap是否為空,為空去new一個ThreadLocalMap對象,存入到當(dāng)前線程的threadLocals。不為空,則獲取threadLocalMap調(diào)用set方法,當(dāng)前ThreadLocal對象為鍵,存入值。當(dāng)調(diào)用threadLocal.get方法時,同樣先判斷當(dāng)前線程的threadLocalMap是否為空,不為空調(diào)用threadLocalMap的getEntry方法,使用當(dāng)前ThreadLocal對象為鍵,取出entry對象里面的value。這樣完成整個操作。

從圖中可以看到,每個線程的存取,都是對自己內(nèi)部唯一的threadLocalMap進(jìn)行操作,ThreadLocal對象只是作為了鍵來存取數(shù)據(jù),根本就沒有用到多線程的操作。

ThreadLocal引發(fā)的內(nèi)存泄漏
static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> var1, Object var2) {
                super(var1);
                this.value = var2;
            }
        }

由于這個ThreadLocal使用weakreference來包裝的,所以在內(nèi)存不足時,threadlocal會被回收,而threadlocalMap在線程中是強(qiáng)引用,一旦threadlocal被回收,在threadlocalmap中對應(yīng)的value就無法再被訪問,就成了垃圾,且不可被回收,就發(fā)生了內(nèi)存泄漏。要解決這個問題,在用完了threadloacal之后,需要調(diào)用threadlocal的remove方法,將threadlocal所指向的value一起從內(nèi)存中清除。

代碼地址:

https://github.com/running-libo/JavaPrinciples

最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一.threadlocal概述 顧名思義線程本地存儲,如果定義了一個threadlocal對象,每個線程往這個th...
    Gorden_Tam閱讀 506評論 0 0
  • ??在學(xué)習(xí)Handler消息機(jī)制中Looper源碼時看到ThreadLocal這個類,發(fā)現(xiàn)它很強(qiáng)大并且很方便的實現(xiàn)...
    Comclay閱讀 170評論 0 0
  • 1. 背景 ThreadLocal源碼解讀,網(wǎng)上面早已經(jīng)泛濫了,大多比較淺,甚至有的連基本原理都說的很有問題,包括...
    小陳阿飛閱讀 1,423評論 2 56
  • ThreadLocal是什么 ThreadLocal提供了線程的局部變量,每個線程訪問獨立的變量副本,實現(xiàn)了線程的...
    Harri2012閱讀 421評論 0 0
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉(zhuǎn)變要...
    余生動聽閱讀 10,798評論 0 11

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