一文搞懂ThreadLocal原理源码解读及应用场景

ThreadLocal是什么?

ThreadLocal是线程本地变量,是一种存储在线程Thead上的ThreadLocalMap结构的变量,通常用于同一个线程内变量传递,或者变量共享。

注意:子线程内要获取父线程set值用 InheritableThreadLocal

使用示例

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("ThreadLocal");
String localValue=threadLocal.get();
threadLocal.remove();

ThreadLocal原理及源码解读

GET原理

get流程:通过Thread.currentThread()获取线程实体Thread,线程变量数据就保存在线程Thread上,是一个ThreadLocalMap(维护一个Entry[] table 数组)的属性变量threadLocals,如果map为空,则设置值为初始值null;如果map不为空,则通过当前线程哈希值计算的存储位置( key.threadLocalHashCode & (table.length - 1))来获取存储的Entry结构(是个保存object的弱引用对象)。

来观赏下源码~~

    
//返回此线程局部变量的当前线程副本中的值。 如果该变量对于当前线程没有值,则首先将其初始化为调用initialValue方法返回的值。
public T get() {
        Thread t = Thread.currentThread();
        //获取线程Thread的属性变量
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
//获取与 ThreadLocal 关联的映射。 在 InheritableThreadLocal 中重写。
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

SET原理

set流程:通过Thread.currentThread()获取线程实体Thread,获取线程Thread的属性变量threadLocals(ThreadLocalMap结构),首次为空时,threadLocals创建ThreadLocalMap实例(维护一个Entry[] table数组,初始容量为16,达到加载因子2/3是2倍扩容 );不为空时,通过线程变量的哈希值对数组长度与计算得到存储位置,并依据此获取数组的指定位置的Entry值,如果是当前线程的则返回覆盖,否则新增,最后判断是否清楚多余曹位及扩容;

//将此线程局部变量的当前线程副本设置为指定值。
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            //首次创建Map数据
            createMap(t, value);
    }

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

划重点

所以线程变量本质是存储在线程java.lang.Thread上的属性变量,通过ThreadLocal类维护ThreadLocalMap结构的属性值:

//JDK1.8 
public class Thread implements Runnable {
//属性省略
....
//与此线程有关的 ThreadLocal 值。该映射由 ThreadLocal 类维护
ThreadLocal.ThreadLocalMap threadLocals = null;

//与此线程有关的 InheritableThreadLocal 值。此映射由 InheritableThreadLocal 类维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

//Thread ID
private long tid;
//返回此线程的标识符。 线程 ID 是在创建该线程时生成的一个长正数。
// 线程 ID 是唯一的,并且在其生命周期内保持不变。 当一个线程终止时,这个线程 ID 可能会被重用
public long getId() {
        return tid;
    }
....
}

看到这里想必好奇ThreadLocalMap到底是个什么结构

ThreadLocalMap 是一种定制的哈希映射,仅适用于维护线程本地值。 不会在 ThreadLocal 类之外导出任何操作。 该类是包私有的,以允许在类 Thread 中声明字段。 为了帮助处理非常大和长期存在的用法,哈希表条目使用 Wea​​kReferences 作为键。 但是,由于不使用引用队列,因此只有在表开始耗尽空间时才能保证删除陈旧条目。

这里列出几个重要的方法和属性:

//JDK1.8
static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
            /** 与此 ThreadLocal 关联的值 */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //存储数组,初始为16,长度必须为2的幂
        private Entry[] table;
        private int threshold;//扩容阈值,初始为长度的2/3

        //初始化
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            //计算存储位置
            //这里重点说下threadLocalHashCode是一个自定义的乘法哈希值
            //当数组长度是2的幂减1的时候,取余等于按位与操作
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
        //获取与key关联的value。
        // 此方法本身仅处理快速路径:直接点击现有密钥。 否则它会到 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);
        }
        //其实关键方法还是在set里面
        private void set(ThreadLocal<?> key, Object value) {

            // 我们不像使用 get() 那样使用快速路径,因为它 
            // 至少与使用 set() 创建新条目一样常见 
            // 它用于替换现有条目,在这种情况下,使用快速 
            // 路径会经常失败。

            Entry[] tab = table;
            int len = tab.length;
            //计算存储位置
            int i = key.threadLocalHashCode & (len-1);

            //拿出值后如果不是当前key,则指针i递增1,继续寻找
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                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)
                //扩容:当达到数组长度2/3是,清理旧条目然后2倍扩容
                //新存储位置计算是重新哈希,如果冲突则位置+1直到找到空槽位
                rehash();
        }
}

ThreadLocal使用场景

1) 对象跨层传递

定义一个通用的线程变量类,不同的方法和业务类,无需参数传递即可使用,代码太长,在另外一篇文章内贴吧,这里给一个hystrix框架的典型应用

package com.netflix.hystrix;
public class Hystrix {
//定义一个连表结构的静态线程变量,保存当前的命令
private static ThreadLocal<LinkedList<HystrixCommandKey>> currentCommand = new ThreadLocal<LinkedList<HystrixCommandKey>>() {
        @Override
        protected LinkedList<HystrixCommandKey> initialValue() {
            return new LinkedList<HystrixCommandKey>();
        }
    };
//当前线程获取其保存的值
public static HystrixCommandKey getCurrentThreadExecutingCommand() {
        if (currentCommand == null) {       return null;}
        return currentCommand.get().peek();
    }
}

2)线程间数据隔离

看一个框架内的使用示例

package com.sun.jersey.core.header;
//HTTP 指定日期格式的帮助程序类
public class HttpDateFormat {
....

//通过一个静态ThreadLocal变量,做到线程隔离,适用于http请求公用操作类。
private static ThreadLocal<List<SimpleDateFormat>> dateFormats = new ThreadLocal<List<SimpleDateFormat>>() {
        @Override
        protected synchronized List<SimpleDateFormat> initialValue() {
            //初始化要用的日期格式
            return createDateFormats();
        }
    };
....
}

3)进行事务操作时,用于存储线程事务信息

4)数据连接,Session会话管理

。。。太多了,代码就不贴了

ThreadLocal小结

  • ThreadLoacl是操作线程变量的操作辅助类,线程变量是存储在线程Thread上的ThreadLocalMap结构的属性变量。
  • 用完一定要记得清除remove,否则容易造成内存泄露
  • 父线程定义ThreadLocal子线程是获取不到,如果父给子线程传递对象用InheritableThreadLocal

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值