ThreadLocal源碼分析

什么是ThreadLocal

ThreadLocal是一個(gè)線程獨(dú)享的本地存放空間,什么是獨(dú)享的本地空間,也就是時(shí)候每個(gè)線程都有一份,
線程之間不能共享該區(qū)域。主要用于存儲(chǔ)線程變量。

什么情況下會(huì)使用ThreadLocal

多線程的情況下,如果存在資源的競(jìng)爭(zhēng)的時(shí)候,我們通常使用鎖的機(jī)制去處理這種資源的競(jìng)爭(zhēng),而對(duì)于一些資源
每個(gè)線程都要有一份的情況下,我們就會(huì)使用ThreadLocal.

例如:

  1. Spring的事務(wù)管理,是通過(guò)C3P0或者其他的數(shù)據(jù)庫(kù)連接池中獲取一個(gè)Connection,然后將Connection存放每個(gè)線程中
    的ThreadLocal中,保證每個(gè)線程之間使用的數(shù)據(jù)庫(kù)連接都是獨(dú)享的。我們可以想下,如果每個(gè)線程都是使用同一個(gè)
    數(shù)據(jù)連接,我們就無(wú)法控制事務(wù)的提交和回滾。因此每個(gè)線程必須獨(dú)占這個(gè)數(shù)據(jù)庫(kù)連接。那如果我們?cè)贒ao層
    獲取Connection,然后執(zhí)行SQL提交事務(wù)。就不會(huì)存在這個(gè)問(wèn)題啦,其實(shí)并不是,我們通常會(huì)在三層中的服務(wù)層進(jìn)行事務(wù)
    的開(kāi)啟和事務(wù)的提交、回滾。那么為什么會(huì)在服務(wù)層進(jìn)行事務(wù)的處理,主要是因?yàn)槭聞?wù)的邊界問(wèn)題。
    因?yàn)镾ervice會(huì)調(diào)用一系列的DAO對(duì)數(shù)據(jù)庫(kù)進(jìn)行多次操作。多個(gè)Dao中我們無(wú)法確定到底哪個(gè)Dao進(jìn)行事務(wù)的提交。
    因此使用ThreadLocal進(jìn)行存放每個(gè)數(shù)據(jù)庫(kù)的連接。
  2. 還有一種場(chǎng)景,在多數(shù)據(jù)源的情況下,我們可以通過(guò)一個(gè)key獲取對(duì)應(yīng)的數(shù)據(jù)源,而這個(gè)key值
    ,從我們ThreadLocal中獲取到對(duì)應(yīng)的數(shù)據(jù)源。從而隔離多線程下數(shù)據(jù)源的訪問(wèn)。

ThreadLocal的使用

  • ThreadLocal的使用,我們只需要?jiǎng)?chuàng)建一個(gè)ThreadLocal并且指定存放的數(shù)據(jù)類(lèi)型即可。
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

  • ThreadLocal類(lèi)的方法
 public T get() 獲取當(dāng)前線程的變量
 public void set(T value)  設(shè)置當(dāng)前線程的變量
 public void remove() 刪除線程變量

ThreadLocal源碼分析

一、ThreadLocal結(jié)構(gòu)分析

ThreadLocal的實(shí)現(xiàn)原理,我們可以通過(guò)ThreadLocal的get方法進(jìn)行跟蹤分析。

1    public T get() {
2        Thread t = Thread.currentThread();
3        ThreadLocalMap map = getMap(t);
4        if (map != null) {
5            ThreadLocalMap.Entry e = map.getEntry(this);
6            if (e != null) {
7                @SuppressWarnings("unchecked")
8                T result = (T)e.value;
9                return result;
10            }
11        }
12        return setInitialValue();
13    }

從ThreadLocal的get方法中我們可以看出,現(xiàn)獲取當(dāng)前的線程,再通過(guò)線程去獲取
對(duì)應(yīng)線程的ThreadLocalMap.從ThreadLocalMap的名字,我們可以看出他是一個(gè)Map的數(shù)據(jù)結(jié)構(gòu)。
我們就想ThreadLocalMap是存放在哪里的呢?我們可以通過(guò)跟中g(shù)etMap的方法中看出,ThreadLocalMap
是存放到Thread中。

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

而Thread中的ThreadLocalMap是在ThreadLocal中進(jìn)行初始化。我可以通過(guò)ThreadLocal中的set方法和setInitialValue方法查看到初始化ThreadLocalMap.

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

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

  void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
image.png
二、ThreadLocalMap
  • 1.存儲(chǔ)結(jié)構(gòu)分析
    我可以從ThreadLocalMap的源碼進(jìn)行查看,ThreadLocalMap跟HashMap等都是使用Entry數(shù)據(jù)結(jié)構(gòu)進(jìn)行存儲(chǔ).
1    static class ThreadLocalMap {
2          static class Entry extends WeakReference<ThreadLocal<?>> {
3              /** The value associated with this ThreadLocal. */
4              Object value;
5  
6              Entry(ThreadLocal<?> k, Object v) {
7                  super(k);
8                  value = v;
9              }
10          }
11
12        private Entry[] table;

那么如果我們?cè)诖a中創(chuàng)建兩個(gè)ThreadLocal會(huì)怎樣存儲(chǔ)的呢?

 public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    public static ThreadLocal<String> threadLocal2 = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "hello";
        }
    };

通過(guò)對(duì)setInitialValue方法進(jìn)行分析發(fā)現(xiàn),我們創(chuàng)建兩個(gè)ThreadLocal,其實(shí)內(nèi)部是將當(dāng)前的ThreadLocal對(duì)象作為鍵值,initialValue的返回值做為Value。

  private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

總結(jié):
我們通過(guò)對(duì)ThreadLocalMap的set和get方法,可以發(fā)現(xiàn),我們ThreadLocalMap是存Thread中,由ThreadLocal的設(shè)置相關(guān)方法進(jìn)行創(chuàng)建。存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)是以ThreadLocal作為鍵保存對(duì)應(yīng)的Value到ThreadLocalMap中。


image.png

我們可以通過(guò)源碼查看到Entry是Key是一個(gè)弱引用,而不是像HashMap那樣使用強(qiáng)引用。那么ThreadLocalMap中的key為弱引用的,那么ThreadLcaoMap的中的值只能存活到下一次的GC。

弱引用:通常用于存儲(chǔ)非必需對(duì)象的,弱引用指向的對(duì)象實(shí)例,在下一次垃圾收集發(fā)生之前是存活的。

    1. ThreadLocalMap內(nèi)存泄漏
      從存儲(chǔ)的結(jié)構(gòu)我們可以發(fā)現(xiàn),ThreadLocal是ThreadLocalMap的鍵值。如果在我們的代碼中將 ThreadLocalRef設(shè)置為null的情況下,我們就不可以通過(guò)ThreadLocal進(jìn)行訪問(wèn),而ThreadLocalMap還是會(huì)保存Entry,而這樣就會(huì)造成內(nèi)存的泄漏。Entry只能等待線程結(jié)束后才能進(jìn)行GC。
import org.junit.Test;


public class UseThreadLocal {
    private static int threadCount = 30;

    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    public static ThreadLocal<String> threadLocal2 = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "hello";
        }
    };

    class ThreadLocalRunable implements Runnable {

        private int threadId;

        public ThreadLocalRunable(int threadId) {
            this.threadId = threadId;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":start"+"thread id  = "+threadLocal.get());
            threadLocal2 = null;
            Integer threadLocalInt = threadLocal.get();
            threadLocal.set(threadLocalInt+this.threadId);
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":end" + " thread id=" + threadLocal.get());
        }
    }

    @Test
    public void test() throws InterruptedException {
        Thread[] threads = new Thread[threadCount];
        for (int i = 0 ;i<threadCount;i++){
            threads[i] = new Thread(new ThreadLocalRunable(i),"線程"+i);
        }
        for (Thread t :threads){
            t.start();
            t.join();
        }

    }
}

為了防止內(nèi)存泄漏,我們可以通過(guò)ThreadLocal提供的remove移除ThreadlocalMap中ThreadLocal的Value。

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

  • 3.為什么ThreadLocalMap中的ThreadLocal使用弱引用
    上面我們說(shuō)過(guò)當(dāng)我們?yōu)門(mén)hreadLocalRef設(shè)置為null的時(shí)候,ThreadLocal會(huì)被回收,而ThreadLocal被調(diào)用的前提是沒(méi)有被其他引用。那么在ThreadLocalMap中會(huì)將ThreadLocal作為鍵值。如果ThreadLocalMap中使用強(qiáng)引用的話,ThreadLocal就一定不會(huì)被回收,就會(huì)造成ThreadLocal的內(nèi)存泄漏,但是ThreadLocalMap使用的是弱引用,那也就是說(shuō)在下一次GC的時(shí)候ThreadLocal就會(huì)被回收,而不會(huì)造成內(nèi)存泄漏。

    1. ThreadLocalMap內(nèi)存溢出
      我們使用多線程是為了提高CPU的使用效率或者將一些耗時(shí)的工作由其他線程進(jìn)行處理。
      那么如果我們線程處理的時(shí)間非常耗時(shí),存放在ThreadLocal中的數(shù)據(jù)非常大,并且存放在Threadlocal中的數(shù)據(jù)沒(méi)有釋放。JVM為了避免內(nèi)存溢出,
運(yùn)行時(shí)設(shè)置堆內(nèi)存為1m
-Xms=1m
-Xmx=1m


public class ThreadLocalOOM {
    private static int threadCount = 30;

    public static ThreadLocal<HashMap> threadLocal = new ThreadLocal<HashMap>() {
        @Override
        protected HashMap initialValue(){
            HashMap map= new HashMap<>();
            for (int i=0;i<1000000000;i++){
                map.put(i,i);
            }
            return map;
        }
    };



    class ThreadLocalRunable implements Runnable {

        private int threadId;

        public ThreadLocalRunable(int threadId) {
            this.threadId = threadId;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":start"+"thread id  = "+threadLocal.get());
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":end" + " thread id=" + threadLocal.get());
        }
    }
    @Test
    public void test() throws InterruptedException {
        Thread[] threads = new Thread[threadCount];
        for (int i = 0 ;i<threadCount;i++){
            threads[i] = new Thread(new ThreadLocalRunable(i),"線程"+i);
        }
        for (Thread t :threads){
            t.start();
            t.join();
        }

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

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

  • 1. 背景 ThreadLocal源碼解讀,網(wǎng)上面早已經(jīng)泛濫了,大多比較淺,甚至有的連基本原理都說(shuō)的很有問(wèn)題,包括...
    時(shí)之令閱讀 701評(píng)論 1 5
  • 一. 簡(jiǎn)介 提醒篇幅較大需耐心。 簡(jiǎn)介來(lái)自ThreadLocal類(lèi)注釋 ThreadLocal類(lèi)提供了線程局部 (...
    BrightLoong閱讀 9,619評(píng)論 2 14
  • ThreadLocal,線程變量,是一個(gè)以ThreadLocal對(duì)象為鍵,任意對(duì)象為值 的存儲(chǔ) 結(jié)構(gòu)。該結(jié)構(gòu)附著于...
    Justlearn閱讀 492評(píng)論 0 2
  • 首先通過(guò)問(wèn)題去看源碼 ThreadLocal通過(guò)空間換取線程變量安全的說(shuō)法正確嗎 ThreadLocal為什么說(shuō)會(huì)...
    Visualing閱讀 278評(píng)論 0 2
  • 寫(xiě)在前面 本次使用的源碼來(lái)自于Android API 25 Platform. 引子 在Android中,在子線程...
    houtrry閱讀 696評(píng)論 0 1

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