一文读懂ThreadLocal「线程变量原理 源码剖析」

在这里插入图片描述

简介


ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。

这里有几点需要注意

因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景

数据结构


image.png

Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap

ThreadLocalMap有自己的独立实现,可以简单地将它key视作ThreadLocalvalue为放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。

线程在往ThreadLocal里放值的时候,都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

ThreadLocalMap有点类似HashMap的结构,只是HashMap是由数组+链表实现的,而ThreadLocalMap中并没有链表结构。

我们还要注意Entry, 它的key是ThreadLocal k ,继承自WeakReference, 也就是我们常说的弱引用类型(当没有强引用的时候,GC会回收弱引用)。

示例



public class DemoThreadLocal {

    private List<String> messages = Lists.newArrayList();

    //ThreadLocal 设置的是一个成员变量,或者外部变量
    public static final ThreadLocal<DemoThreadLocal> holder = ThreadLocal.withInitial(DemoThreadLocal::new);

    public static void add(String message) {
        holder.get().messages.add(message);
    }

    public static List<String> clear() {
        List<String> messages = holder.get().messages;
        holder.remove();

        System.out.println("size: " + holder.get().messages.size());
        return messages;
    }

    public static void main(String[] args) {
        DemoThreadLocal.add("一枝花算不算浪漫");
        System.out.println(holder.get().messages);
        DemoThreadLocal.clear();
     }
}

常用方法

set()

图解
image.png

Hash冲突
ThreadLocalMap 不像 hashMap,有 数组 + 链表 的结构解决hash冲突,ThreadLocalMap hash冲突 时候,会在原有table[] 数组往后线性查找到空的位置放进去。

扩容机制

1.执行rehash()方法,对过期的key清除

2.第一步执行完后,通过 size >= threshold * 3/4 判断是否要进行扩容

3.扩容的大小是 oldLen * 2

image.png

get()

图解

image.png

1.ThreadMap中,key是个弱引用,如果调用set()方法后,线程执行完后,当前线程就没有强引用这个key了,gc时候就key会被直接回收。value不是弱引用,会按正常回收。

2.应用场景MDC, 分布式日志中的traceId, 请求的时候生成traceId放到ThreadLocal中, 调用下个链路请求时候,在请求head放入id,下游再取出traceId放入ThreadLocal

关键源码

 public class ThreadLocal<T> {
        
        //涉及到 ThreadLocalMap 的哈希值计算
        private final int threadLocalHashCode = nextHashCode();    
        private static AtomicInteger nextHashCode = new AtomicInteger();    
        private static final int HASH_INCREMENT = 0x61c88647;
        
        //放入值, 若果map还不存在,则直接新建一个
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                map.set(this, value);
            } else {
                createMap(t, value);
            }
        }
        
        //获取值,拿到当前线程的ThreadLocalMap
        public T get() {
            Thread t = Thread.currentThread();
            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();
        }
        
        //创建ThreadLocalMap
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
        
        //获取当前线程map
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }

        //ThreadLocalMap 中hash值增长
        private static int nextHashCode() {
            return nextHashCode.getAndAdd(HASH_INCREMENT);
        }

        //ThreadLocal 中维护的map对象
        static class ThreadLocalMap {
            
            //Entry对象是个弱引用,作为 ThreadLocalMap 的key,没有强引用的时候,会被gc直接回收
            static class Entry extends WeakReference<ThreadLocal<?>> {
                
                //value是具体存进map中的value
                Object value;
        
                //构造函数
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
                
            //ThreadLocalMap 的初始化大小    
            private static final int INITIAL_CAPACITY = 16;
                
            private Entry[] table;

            //构造函数        
            ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                table = new Entry[INITIAL_CAPACITY];
                //这里会调用 nextHashCode 获取
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

                table[i] = new Entry(firstKey, firstValue);
                size = 1;
                setThreshold(INITIAL_CAPACITY);
            }
            
        }
        
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杭州水果捞|Java毕业设计成品

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值