美文网首页
多线程-ThreadLocal

多线程-ThreadLocal

作者: 麦大大吃不胖 | 来源:发表于2020-12-07 14:13 被阅读0次

by shihang.mai

1. 线程隔离原理

1.1 原理解析

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }
ThreadLocal线程隔离原理.png

Thread类有一个类型为ThreadLocalMap的变量,观察ThreadLocal实例的set方法,可以看到当set值时,其实是Thread的ThreadLocalMap,向里面set值。

下面举例说明

  • 有ThreadLocal变量a、b
  • 线程A调用a.set(),b.set()。线程B调用a.set(),b.set()
线程隔离
  1. 线程A调用a.set和b.set,线程B调用a.set和b.set,都会先获取当前线程的threadLocals,然后将值设置进去,关系图如上
  2. 而当前线程调用a.get或者b.get,都先获取threadLocals,然后通过当前的ThreadLocal实例,获取到对应的值

操作的都是线程自身的变量,做到线程隔离

1.2 ThreadLocalMap是ThreadLocal的内部类意义

注释写得很清楚了,ThreadLocalMap 就是为维护线程本地变量而设计的,只做这一件事情。

1.3 为什么key是ThreadLocal,而不是Thread对象

  1. 如果key是Thread对象的话,那么我再保存一个另外的值,不就把原来的值覆盖了吗,显然不行。
  2. 如果要再保存另外一个值,直接新建一个key,即新建ThreadLocal即可

1.4 ThreadLocal数据结构

ThreadLocalMap is a customized hash map suitable only for maintaining thread local values

注释也写得很清楚了,就是HashMap。但是遇到hash冲突怎么办呢,这个和hashmap有啥区别呢

  1. hashmap由数组+链表+红黑树组成。而ThreadLocalMap没链表也没红黑树
  2. 当ThreadLocalMap遇到hash冲突时,用的开放地址法,即看看相邻位置有没空位,有就加入。没就一直找到空的,把元素放进去。元素个数超数组长度就扩容

1.5 key设计成弱引用意义

为了尽最大努力避免内存泄漏,而不是直接避免了内存泄漏
因为只有当ThreadLocal对象只被WeakReference引用时,才会被gc回收。当还有强引用指向ThreadLocal对象,gc是不会回收的

ThreadLocal线程隔离原理.png

问题就来了:那既然ThreadLocal对象有强引用,回收不掉,干嘛还要设计成WeakReference类型呢

  1. 按我们的使用习惯,当ThreadLocal = null 时,就意味着ThreadLocal要被回收,如果设计成强引用,那么这个语义就没了。
  2. 如果Threadlocal对象一直有强引用,就有内存泄漏风险,所以我们手动调用ThreadLocal.remove()

2. 子线程获取父线程设置的本地变量

使用InheritableThreadLocal即可

static InheritableThreadLocal a  =new InheritableThreadLocal();

    public static void main(String[] args) {
        
        a.set("a");
        Msh msh = new Msh();
        msh.start();
    }

    static class Msh extends Thread{
        
        @Override
        public void run() {
            System.out.println(a.get());
            
        }
    }

Thread类有一个类型为ThreadLocalMap的变量inheritableThreadLocals

  1. 当我们使用InheritableThreadLocal,无论get或者set都会设置Thread的变量inheritableThreadLocals
  2. 父线程调用set,会将值保存到inheritableThreadLocals,父线程创建子线程时,会将自身的inheritableThreadLocals重新复制一份给子线程的inheritableThreadLocals,这样子线程就获取到了父线程设置的本地变量

3. 内存泄漏

ThreadLocal线程隔离原理.png

前提:线程一直保留,即使用线程池

  1. 如果ThreadLocal不存在外部强引用时,Key会被GC回收,这样就会导致ThreadLocalMap中key为null, 而整一个Entry还存在强引用,即key为null,但是value还在
  2. GC永远无法回收,造成内存泄漏
  3. 这种key为null,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除

所以:内存泄漏的根源是由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用

参考

https://mp.weixin.qq.com/s/8OEJB_qqzxFeXT-jkGB26g

相关文章

网友评论

      本文标题:多线程-ThreadLocal

      本文链接:https://www.haomeiwen.com/subject/iolziktx.html