并发第五弹 ThreadLocal原理

ThreadLocal大家都知道,线程本地存储,先来简单看一下ThreadLocal的基本使用。


public class ThreadLocalTest {
    ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        ThreadLocalTest tlt = new ThreadLocalTest();
        new Thread(()->{
            // 设置值
            tlt.threadLocal.set("hello");
            // 获取值
            System.out.println(tlt.threadLocal.get());
            // 如果不用了,要及时清除掉
            tlt.threadLocal.remove();
        }).start();
    }
}


这里提一下为什么要记得使用remove方法:

  • 如果当前线程一直不消亡, 那么这些本地变量会一直存在, 所以可能会造成内存溢出, 因此使用完毕后要记得调用ThreadLocal 的remove 方法删除对应的本地变量;


ThreadLocal的使用看起来如此简单,下面带大家了解一下ThreadLocal的原理。


其实Thread类里有两个变量,如下。


// 与ThreadLocal变量有关
ThreadLocal.ThreadLocalMap threadLocals = null;
// 与InheritableThreadLocal变量有关
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;


InheritableThreadLocal是ThreadLocal的子类,我们后面再讲,先重点关注ThreadLocal即可。

为什么要列举出Thread类里面的这两个变量呢?当然是因为它们与ThreadLocal的原理有关!


ThreadLocal的使用无非就是get()、set()、remove()方法,下面我们先看一下ThreadLocal::set()方法的源码


public void set(T value) {
  // 获取当前线程
  Thread t = Thread.currentThread();
  // 获取当前线程的threadLocals变量
  ThreadLocalMap map = getMap(t);
  if (map != null)
    // 设置值
    map.set(this, value);
  else
    // 创建threadLocals,并设置值
    createMap(t, value);
}

// 获取指定线程的threadLocals变量
ThreadLocalMap getMap(Thread t) {
  return t.threadLocals;
}



通过上面的源码分析,我们有如下结论:

  • ThreadLocal其实只是一个空壳,而所谓的线程本地变量其实是存储在Thread类的threadLocals变量里的

  • threadLocals是ThreadLocal.ThreadLocalMap类型,ThreadLocal::set调用ThreadLocalMap::set将线程本地变量放置到threadLocals中;

  • Thread的threadLocals变量初始值是null,当第一次设置值的时候才会实例化;


下面我们再来深入看一下ThreadLocalMap::set()方法的源码。


public class ThreadLocal<T> {
    static class ThreadLocalMap{
    // ThreadLocal是一个弱引用,一旦发生垃圾回收就会清除
    static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
  
    // 哈希表,为什么要设计成哈希表呢?因为每个线程可以有多个ThreadLocal变量
    private Entry[] table;
  
    private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
              // 哈希函数,做&运算,获取下标
            int i = key.threadLocalHashCode & (len-1);
      
               // 注意看这个for循环,用的是线性探测法
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
        
                    // 表示这个ThreadLocal是第二次设置值,则新值会覆盖旧值
                if (k == key) {
                    e.value = value;
                    return;
                }
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    // 内部会调用resize()方法
                rehash();
        }
  }
}


通过上述源码和注释,我们了解到ThreadLocalMap维护了一个使用线性探测法的哈希表Entry[]来存储多个线程本地变量,下标计算方式=ThreadLocal::threadLocalHashCode() & (length-1),这里的hashcode其实是通过AtomicInteger获取的。因此,ThreadLocalMap是一个定制化的Map

而至于线程如何将threadLocals变量存储在线程本地而不共享,就不是Java层面的事,与本地库c或者c++的实现有关了。


而ThreadLocal有一个问题,就是子线程无法获取父线程的ThreadLocal变量,即不可继承。因为子线程与父线程是两个线程,因此无法获取是很正常的。

而前面提到的InheritableThreadLocal就是来解决这个问题的,InheritableThreadLocal是可继承的。


ThreadLocal不可继承性与InheritableThreadLocal可继承性示例代码如下。


public class ThreadLocalTest {
    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        ThreadLocalTest tlt = new ThreadLocalTest();
        tlt.threadLocal.set("ok");
        tlt.inheritableThreadLocal.set("123");
    
        new Thread(()->{
            //获取父线程的值
            // 输出null
            System.out.println(tlt.threadLocal.get());
            // 输出123
            System.out.println(tlt.inheritableThreadLocal.get());

        }).start();
    }
}



原文始发于微信公众号(初心JAVA):并发第五弹 ThreadLocal原理

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/35544.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之家——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!