• ThreadLocal源码解读,干就完事了


    ThreadLocal 源码

    ThreadLocal 提供了线程局部变量,比如我在线程A通过ThreadLocal Set一个值进去,那么在这个线程的执行过程中,我们在任何方法里都能取到这个值。

    如果在这个线程中开辟的子线程里面,是取不到这个值的,ThreadLocal只能作用于当前线程。

    这就涉及到了ThreadLocal的原理,虽然我们可以在任何地点都能new一个ThreadLocal出来,但是通过ThreadLocal Set的变量最终是存放在当前线程的threadLocals的Map结构中,Map的key是ThreadLocal的实例,我们可以从源码里面看到相关的处理

    private void set(ThreadLocal<?> key, Object value) {
    
        Entry[] tab = table;
        int len = tab.length;
        int i = key.threadLocalHashCode & (len-1);
        // 如果tab[i]不为空
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            // 存在引用,直接替换值
            if (k == key) {
                e.value = value;
                return;
            }
            // 替换过期的k
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        // 为空,直接存放,增加size
        tab[i] = new Entry(key, value);
        int sz = ++size;
        //检测是否需要调整table大小
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            //重新调整大小
            rehash();
    }
    
    public T get() {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 从当前线程获取 ThreadLocalMap 
        // 一个Map结构,Key=>ThreadLocal对象,value=>通过ThreadLocal Set进去的值
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // ***** 核心方法
            // 从当前线程的ThreadLocalMap获取Entry,这里的参数this就是当前ThreadLocal对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 不为空就返回
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 如果Map为空,这里会创建一个ThreadLocalMap
        return setInitialValue();
    }
    
    private Entry getEntry(ThreadLocal<?> key) {
        // 通过threadLocalHashCode 和 表长度-1 的与运算,得出table的下标
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        // e.get()说明:Entry继承了WeakReference,所以e.get() 获取的是e的引用对象,也就是key
        if (e != null && e.get() == key)
            return e;
        else
            // 如果找不到,就从e的位置继续向后找
            return getEntryAfterMiss(key, i, e);
    }
    
    //查找对象
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;
    
        while (e != null) {
            //获取Entry的引用对象ThreadLocal
            ThreadLocal<?> k = e.get();
            // 相同直接返回
            if (k == key)
                return e;
            // 如果k为空,这是一个过时的数据,执行清理
            if (k == null)
                expungeStaleEntry(i);
            else
                // 移动下标,继续查找,一直找到链表的头,如果没有就跳出循环
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }
    
    // 清理过期数据,重新计算hash值
    private int expungeStaleEntry(int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;
    
        // 直接清理过时的数据
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        //长度减一
        size--;
    
        // 继续查找,清理 并 重新计算hash, 直到遇到null
        Entry e;
        int i;
        for (i = nextIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = nextIndex(i, len)) {
            // 如果为空,就清理,并将size减1
            ThreadLocal<?> k = e.get();
            if (k == null) {
                e.value = null;
                tab[i] = null;
                size--;
            } else {
                // 不为空 ,就重新计算hash
                int h = k.threadLocalHashCode & (len - 1);
                // 新计算的hash于当前Hash不一致时
                if (h != i) {
                    tab[i] = null;
                    // 与 Knuth 6.4 算法 R 不同,我们必须扫描直到为空,因为多个条目可能已经过时。
                    // 如果h位置不为空时,需要重新计算,直到h位置为空
                    while (tab[h] != null)
                        h = nextIndex(h, len);
                    //将i位置的e对象放入h位置
                    tab[h] = e;
                }
            }
        }
        return i;
    }
    

    针对threadLocalHashCode的说明

    ThreadLocals 的是实现依赖于每个线程中(Thread.threadLocals 和inheritableThreadLocals)的哈希映射。

    ThreadLocal 对象充当键,通过 threadLocalHashCode 进行搜索。

    这是一个自定义哈希代码(仅在 ThreadLocalMaps 中有用),它消除了在相同线程使用连续构造的 ThreadLocals 的常见情况下的冲突,同时在不太常见的情况下保持良好行为。

    InheritableThreadLocal 原理

    InheritableThreadLocal 继承了 ThreadLocal

    重要的方法

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

    ThreadLocalgetMap获取的是Thread.threadLocalsInheritableThreadLocal 获取的是Thread.inheritableThreadLocals

    这就是ThreadLocalInheritableThreadLocal 最大的不同

    如何实现子线程里有父线程的对象?

    主要取决与创建线程时的初始化方法

    java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        // 不相干的代码删掉了,不是方法的全部源码
        
        //当前线程,也就是创建线程的线程 = 父线程
        Thread parent = currentThread();
        //inheritThreadLocals = true,并且父线程的inheritableThreadLocals不为空,就复制到子线程中
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    }
    

    总结:

    1.ThreadLocalInheritableThreadLocal操作的线程变量是不一样的,所以这两个变量的值是不共享。

    2.InheritableThreadLocal通过重新getMap的方式,将ThreadLocalthreadLocals替换为inheritableThreadLocals,其他逻辑完全一样。

    3.InheritableThreadLocal在子线程创建的时候进行同步,实现代码在Thread#init方法中

    4.ThreadLocal的生命周期和Thread一样长,如果不及时remove掉,会造成内存泄漏

  • 相关阅读:
    linux系统备份
    VNC轻松连接远程Linux桌面
    Cacti监控服务器配置教程(基于CentOS+Nginx+MySQL+PHP环境搭建)
    Linux tar命令高级用法——备份数据
    在linux下使用debugfs恢复rm删除的文件
    Linux系统MySQL开启远程连接
    查看LINUX进程内存占用情况
    JavaScript使用数组
    JavaScript计时器
    大话三层架构
  • 原文地址:https://www.cnblogs.com/inkyi/p/14930778.html
Copyright © 2020-2023  润新知