• ThreadLocal线程变量使用浅解


    一、概述
       ThreadLocal一般称为线程本地变量,它是一种特殊的线程绑定机制,将变量与线程绑定在一起,为每一个线程维护一个独立的变量副本。通过ThreadLocal可以将对象的可见范围限制在同一个线程内,

    在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。
      需要重点强调的的是,不要拿ThreadLocal和synchronized做类比,sysnchronized是一种互斥同步机制,是为了保证在多线程环境下对于共享资源的正确访问,用于数据共享。而ThreadLocal从本质上讲,无非是提供了一个“线程级”的变量作用域,它是一种线程封闭(每个线程独享变量)技术,用于数据隔离。

    二、实例
      ThreadLocal类使用的4个方法
    void set(Object value);   // 设置当前线程的线程局部变量的值。
    public Object get();    // 该方法返回当前线程所对应的线程局部变量。
    public void remove();   //将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
    protected Object initialValue();   //返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
      示例代码如下:

    public class ThreadLocalTest {
    
        static ThreadLocal<String> localVar = new ThreadLocal<>();
    
        static void print(String str) {
            //打印当前线程中本地内存中本地变量的值
            System.out.println(str + " :" + localVar.get());
            //清除本地内存中的本地变量
            localVar.remove();
        }
    
        public static void main(String[] args) {
            Thread t1  = new Thread(new Runnable() {
                @Override
                public void run() {
                    //设置线程1中本地变量的值
                    localVar.set("localVar1");
                    //调用打印方法
                    print("thread1");
                    //打印本地变量
                    System.out.println("after remove : " + localVar.get());
                }
            });
    
            Thread t2  = new Thread(new Runnable() {
                @Override
                public void run() {
                    //设置线程1中本地变量的值
                    localVar.set("localVar2");
                    //调用打印方法
                    print("thread2");
                    //打印本地变量
                    System.out.println("after remove : " + localVar.get());
                }
            });
    
            t1.start();
            t2.start();
        }
    }
    
    输出如下:
    
    thread1 :localVar1
    thread2 :localVar2
    after remove : null
    after remove : null
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    三、原理
      1、ThreadLocal使用
      下面是ThreadLocal的类图结构
      在这里插入图片描述

    从图中可知:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量。

    ThreadLocalMap实际上类似于一个HashMap,在默认情况下,每个线程中的这两个变量都为null,只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们。

    除此之外,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量,即在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。。

    set方法源码:
    
    public void set(T value) {
        //(1)获取当前线程(调用者线程)
        Thread t = Thread.currentThread();
        //(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
        ThreadLocalMap map = getMap(t);
        //(3)如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
        if (map != null)
            map.set(this, value);
        //(4)如果map为null,说明首次添加,需要首先创建出对应的map
        else
            createMap(t, value);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    get方法源码:
    
    public T get() {
        //(1)获取当前线程
        Thread t = Thread.currentThread();
        //(2)获取当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
        return setInitialValue();
    }
    
    private T setInitialValue() {
        //protected T initialValue() {return null;}
        T value = initialValue();
        //获取当前线程
        Thread t = Thread.currentThread();
        //以当前线程作为key值,去查找对应的线程变量,找到对应的map
        ThreadLocalMap map = getMap(t);
        //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
        if (map != null)
            map.set(this, value);
        //如果map为null,说明首次添加,需要首先创建出对应的map
        else
            createMap(t, value);
        return value;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    ThreadLocalMap的数据结构,其实是个Entry类型的数组,每个Entry节点都保存一个键值对(key为ThreadLocal 实例的弱引用)。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。

    2、ThreadLocalMap使用
      类的构造函数有两个,一个为public方法,一个为private方法:

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
         // 初始长度为16的Entry数组
         table = new Entry[INITIAL_CAPACITY];
         // 用每个key的threadLocalHashCode和(1111)按位做与操作得到Entry应该放的下标
         // 这样做的好处就是不管你threadLocalHashCode再大,计算结果也不会超过15
         int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
         // 初始化Entry,并放入数组对应位置
         table[i] = new Entry(firstKey, firstValue);
         // Entry数组大小更改为1
         size = 1;
         // 根据容量重设扩容阈值
         setThreshold(INITIAL_CAPACITY);
     }
     这个方法就是传入第一个ThreadLocal对象作为key,value作为值,构建一个ThreadLocalMap。
     需要注意的是,ThreadLocalMap是懒创建的,也就是说直到有Entry需要加入才会调用此方法。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];
     
        for (int j = 0; j < len; j++) {
            Entry e = parentTable[j];
            if (e != null) {
                @SuppressWarnings("unchecked")
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                if (key != null) {
                    Object value = key.childValue(e.value);
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    table[h] = c;
                    size++;
                }
            }
        }
    }
    
    这个构造方法由createInheritedMap方法调用,传入的参数是父线程的ThreadLocalMap。
    将会创建一个ThreadLocalMap包括所有父map内的ThreadLocal
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    ThreadLocalMap扩容方法相关

    // 设置扩容阈值为容量的三分之二
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
     
    // 下标i+1的方式从小往大下标方向递推
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }
     
    // 下标i-1的方式从大往小下标方向递推
    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }
     
    // 将Entry数组扩容为以前大小的两倍,顺便清理发生hash碰撞时key已为null的entry。
    // 具体做法是
    // 1.创建新的Entry[]
    // 2.然后循环遍历老的Entry[]
    // 3.如果Entry元素存在就去获取弱引用的ThreadLocal实例,
    // 4.1此时如果ThreadLocal实例已经为空,代表已经被GC回收,那么直接把当前Entry置为null;
    // 4.2.1否则通过threadLocalHashCode和(新的数组长度-1)按位与的方式得到新的数组下标
    // 4.2.2接着判断该下标位置是否已存在元素,若存在就反复调用nextIndex方法求新的下标
    // 4.2.3将元素entry放入新的数组中的下标位置,并将临时元素计数器加一
    // 5.更新扩容阈值,并将临时变量赋值给对应的实例变量。扩容完成。
    private void resize() {
        // 1.创建新的Entry[],长度为旧数组两倍
        Entry[] oldTab = table;
        int oldLen = oldTab.length;
        int newLen = oldLen * 2;
        Entry[] newTab = new Entry[newLen];
        int count = 0;
        // 2.然后循环遍历老的Entry[]
        for (int j = 0; j < oldLen; ++j) {
            Entry e = oldTab[j];
            if (e != null) {
                // 3.如果Entry元素存在就去获取弱引用的ThreadLocal实例,
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    // 4.1此时如果ThreadLocal实例已经为空,代表已经被GC回收,那么直接把当前Entry置为null;
                    e.value = null; // Help the GC
                } else {
                    // 4.2.1否则通过threadLocalHashCode和(新的数组长度-1)按位与的方式得到新的数组下标
                    int h = k.threadLocalHashCode & (newLen - 1);
                    // 4.2.2接着判断该下标位置是否已存在元素,若存在就反复调用nextIndex方法求新的下标
                    while (newTab[h] != null)
                        h = nextIndex(h, newLen);
                    // 4.2.3将元素entry放入新的数组中的下标位置,并将临时元素计数器加一
                    newTab[h] = e;
                    count++;
                }
            }
        }
        // 5.更新扩容阈值,并将临时变量赋值给对应的实例变量。扩容完成。
        setThreshold(newLen);
        size = count;
        table = newTab;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    放入元素

    private void set(ThreadLocal<?> key, Object value) {
        // We don't use a fast path as with get() because it is at
        // least as common to use set() to create new entries as
        // it is to replace existing ones, in which case, a fast
        // path would fail more often than not.
         
        Entry[] tab = table;
        int len = tab.length;
        int i = key.threadLocalHashCode & (len-1);
        // 寻找新entry放入的适合位置。
        // hash冲突时再hash方式为nextIndex从下标小的方式往大的方向递推
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            // 如果计算出的下标位置存在entry
            // 且该ThreadLocal实例key和参数ThreadLocal相同,那就更新value。set结束
            if (k == key) {
                e.value = value;
                return;
            }
            // 如果计算出的下标位置存在entry且该ThreadLocal实例key为null
            // 此时说明该entry的弱引用已经失效,就用生成新的entry替换。set结束
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
            // 如果计算出的下标位置存在entry且该ThreadLocal实例key和参数ThreadLocal不同,就进入下一次循环
            // 否则说明该位置e = null ,跳出循环
        }
        // 此时e = null,也就是说该数组位置不存在entry
        // 用参数生成一个新的entry,放入此位置
        tab[i] = new Entry(key, value);
        // 大小加一
        int sz = ++size;
        // 如果 且 当前数组元素个数达到扩容阈值,就需要rehash
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    取出元素

    // 获取ThreadLocal实例对应的Entry
    // 如果没能匹配到就调用getEntryAfterMiss
    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }
     
    // 当没有找到ThreadLocal实例对应的Entry时调用该方法
    // 这个方法也会在查找过程中顺便清理无效的弱引用Entry
    // i为数组下标,e为当前下标对应的Entry
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;
     
        while (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == key)
                return e;
            if (k == null)
                expungeStaleEntry(i);
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    替换失效Entry

    // 替换失效的数组位置上的Entry,在此过程中将遇到的失效弱引用Entry移除
    // key和value为新的值,staleSlot为匹配到的第一个失效Entry下标
    private void replaceStaleEntry(ThreadLocal<?> key, Object value,  int staleSlot) {
       Entry[] tab = table;
       int len = tab.length;
       Entry e;
     
       // 倒推以检查当前运行中的已失效Entry。 
       // 我们一次清理整个运行,以避免由于垃圾收集器释放串联的refs(即,每当收集器运行时)不断的增量重复。
       int slotToExpunge = staleSlot;
       // 这里得到的slotToExpunge是从后往前推的最后一个e!=null但是e.get()==null的下标
       for (int i = prevIndex(staleSlot, len);
            (e = tab[i]) != null;
            i = prevIndex(i, len))
           if (e.get() == null)
               slotToExpunge = i;
     
       // 找到的key或尾部的空元素
       for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null;  i = nextIndex(i, len)) {
           ThreadLocal<?> k = e.get();
          
           // 如果匹配到键,那么我们需要将它与失效Entry交换以维护哈希表顺序。 
           // 然后可以将新陈旧的插槽或其上方遇到的任何其他陈旧插槽发送到expungeStaleEntry以删除或重新运行运行中的所有其他条目。
           if (k == key) {
                // 如果存在该ThreadLocal实例的Entry,就覆盖该value
               e.value = value;
               // 这里staleSlot为匹配到的第一个失效Entry下标
               // 赋值后tab[i]!=null但是tab[i].get()==null
               tab[i] = tab[staleSlot];
               // 原staleSlot Entry替换为value更新后的e
               tab[staleSlot] = e;
     
               // slotToExpunge == staleSlot的情况是在前面倒推运算中没有找到失效的Entry
               if (slotToExpunge == staleSlot)
                    // 注意这里i下标对应的元素是tab[staleSlot]
                   slotToExpunge = i;
               // 移除slotToExpunge位置的Entry顺便移除一些失效Entry
               cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
               return;
           }
     
           // 该Entry弱引用失效,且在前面倒推查找中没有匹配到失效的Entry
           if (k == null && slotToExpunge == staleSlot)
                 // 那就把当前这个对应失效弱引用的i给slotToExpunge,然后继续循环
               slotToExpunge = i;
       }
     
       // 走到这里说明没有匹配到ThreadLocal key
       // 把staleSlot处的value设为空(help gc)
       tab[staleSlot].value = null;
       // 该位置设为由新的ThreadLocal实例为key,新value的Entry
       tab[staleSlot] = new Entry(key, value);
     
       // 不相等说明找到了另外的失效的Entry位置,干掉他们并顺便清理出一点空间
       if (slotToExpunge != staleSlot)
           cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    自动清理过期

    // 清理一些Entry
    // 清理次数由当前数组大小是2的多少倍决定
    private boolean cleanSomeSlots(int i, int n) {
        boolean removed = false;
        Entry[] tab = table;
        int len = tab.length;
        // 找到下一个下标位置,如果失效entry就干掉
        do {
            i = nextIndex(i, len);
            Entry e = tab[i];
            if (e != null && e.get() == null) {
                n = len;
                removed = true;
                i = expungeStaleEntry(i);
            }
        } while ( (n >>>= 1) != 0);
        return removed;
    }
     
     
    // 先清理所有失效的Entry
    // 如果 此时size还是大于之前的四分之三,就扩容
    private void rehash() {
        expungeStaleEntries();
     
        if (size >= threshold - threshold / 4)
            resize();
    }
     
    // 遍历数组,移除失去弱引用的旧Entry
    private void expungeStaleEntries() {
         Entry[] tab = table;
         int len = tab.length;
         for (int j = 0; j < len; j++) {
             Entry e = tab[j];
             // 找到失去弱引用的Entry,将该Entry所在下标传入expungeStaleEntry将其干掉
             if (e != null && e.get() == null)
                 expungeStaleEntry(j);
         }
    }
    // 此方法是真正移除失去弱引用的Entry的方法,顺便移除遇到的碰撞位置的失效Entry
    // 参数staleSlot 是该Entry所在下标
    // 返回下标i,此时tab[i]为null
    private int expungeStaleEntry(int staleSlot) {
        Entry[] tab = table;
        // 数组原始长度
        int len = tab.length;
     
        // 移除Entry的value和本身的引用
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        // 数组大小减一
        size--;
     
        Entry e;
        int i;
        // 循环的方式去掉弱引用失效的entry
        // 循环开始条件是让该下标对原始数组长度求模
        // 循环结束条件是Entry为null
        // 每次循环完又再次nextIndex计算新下标
        for (i = nextIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                // 找到弱引用失效的entry,干掉
                e.value = null;
                tab[i] = null;
                size--;
            } else {
                // 此时e的弱引用存在
                 
                // 我们已经熟悉了这种获取下标方式
                int h = k.threadLocalHashCode & (len - 1);
                // 不相等表示存在hash冲突,放入的位置是用N次nextIndex计算后的新位置
                // 这样做的目的我猜是因为这个位置已经发生冲突,所以认为此位置是易冲突位置
                // 所以干脆把该位置的entry放到新的null位置
                // 这样可以使下次放入元素时发生冲突的几率降低
                if (h != i) {
                    // 将e的来自于数组的强引用去掉
                    tab[i] = null;
     
                    // 找到一个新的无entry的数组下标
                    while (tab[h] != null)
                        h = nextIndex(h, len);
                    // 将e移动到新的数组下标位置
                    tab[h] = e;
                }
            }
        }
        // 此时tab[i]为空
        return i;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    四、继承性
      同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals中为当前调用线程对应的本地变量)。但是InheritableThreadLocal类则可以使子类访问父类的本地变量,下面是该类的源码:

    public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    
        protected T childValue(T parentValue) {
            return parentValue;
        }
    
        ThreadLocalMap getMap(Thread t) {
           return t.inheritableThreadLocals;
        }
    
        void createMap(Thread t, T firstValue) {
            t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
        }
    }
    从上面代码可以看出,InheritableThreadLocal类继承了ThreadLocal类,并重写了childValue、getMap、createMap三个方法。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    五、内存泄漏
      在前面的介绍中,可以知道ThreadLocal只是一个工具类,它为用户提供get、set、remove接口操作实际存放本地变量的threadLocals(调用线程的成员变量),也知道threadLocals是一个ThreadLocalMap类型的变量,首先了解下几个概念:

    强引用: Java中默认的引用类型,一个对象如果具有强引用那么只要这种引用还存在就不会被GC。
    软引用: 如果一个对象具有弱引用,在JVM发生OOM之前(即内存充足够使用),是不会GC这个对象的;只有到JVM内存不足的时候才会GC掉这个对象。
    弱引用: 如果一个对象只具有弱引用,那么这个对象就会被垃圾回收器GC掉(被弱引用所引用的对象只能生存到下一次GC之前,当发生GC时候,无论当前内存是否足够,弱引用所引用的对象都会被回收掉)。
    虚引用: 虚引用是所有引用中最弱的一种引用,其存在就是为了将关联虚引用的对象在被GC掉之后收到一个通知。(不能通过get方法获得其指向的对象)
    在这里插入图片描述

    ThreadLocalMap的数据结构,其实是个Entry类型的数组,它是继承自WeakReference的一个类,每个Entry节点都保存一个键值对,该类中实际存放的key是指向ThreadLocal的弱引用和与之对应的value值(该value值就是通过ThreadLocal的set方法传递过来的值)

    考虑这个ThreadLocal变量没有其他强依赖,如果当前线程还存在,由于线程的ThreadLocalMap里面的key是弱引用,所以当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用在gc的时候就被回收,但是对应的value还是存在的这就可能造成内存泄漏。所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量

    六、场景
      最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。实际上也是多线程的一种方式,是用空间换取时间,适合多线程但是线程变量不共享,每个线程的值各自独立的情况。ThreadLocal通常用private static修饰,可以将状态与该线程建立一对一的关系。

    比如Spring的事务管理,用ThreadLocal存储Connection,从而各个DAO可以获取同一Connection,可以进行事务回滚,提交等操作。

  • 相关阅读:
    Neo4j图数据库_web页面关闭登录实现免登陆访问_常用的cypher语句_删除_查询_创建关系图谱---Neo4j图数据库工作笔记0013
    QECon大会亮相产品,全栈测试平台推荐:RunnerGo
    基于神经网络的语音识别
    带有@Transactional注解的方法事务失效问题以及解决方法
    3分钟火速手写一个二叉查找树,搞快点。
    AI 大框架基于python来实现基带处理之TensorFlow(信道估计和预测模型,信号解调和解码模型)
    typeScript简单封装axios
    npm install常见错误的完整指南
    POI操作Word组件.haiwei-poi-word(模板+数据)
    【分享】Excel“只读方式”的两种模式
  • 原文地址:https://blog.csdn.net/weixin_45817985/article/details/133700700