首页 > 其他分享 >ThreadLocal原理(二)

ThreadLocal原理(二)

时间:2024-08-04 22:53:10浏览次数:16  
标签:map ThreadLocalMap value ThreadLocal 线程 key 原理

ThreaddLocal源码方法不是很多,主要有get()方法,set(T value)方法,remove()方法,initialValue()方法.

set(T value)方法

set方法用于设置线程本地变量的值.源码如下.

public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //判断这个map是否存在.
        if (map != null) {
        //将需要设置的值,放进map.
            map.set(this, value);
        } else {
            //创建一个map并且赋值.
            createMap(t, value);
        }
    }
getMap()方法如下,从线程里获取对应的map.
 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
createMap()方法如下,创建了一个ThreadLocalMap赋值.
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
set(T value)方法流程

1:获取当前线程,然后获得当前线程ThreadLocalMap成员.赋值给map.

2:如果map不为空,将值放入map中.然后将当前ThreadLcoal作为key存入.

3:如果map为空的话,然后为该线程创建一个ThreadLocalMap.key为当前的ThreadLcoal.value为set方法的参数value.

get()方法

get方法用于获取当前线程变量的值.源码如下.
public T get() {
        //获取当前线程.
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLcoalMap.
        ThreadLocalMap map = getMap(t);
        //如果map不为null的话,就以当前的threadLocal作为key取出value.
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果为空的话,就会返回初始值.
        return setInitialValue();
    }

 setInitialValue()方法如下,设置初始值并返回.

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }
get()方法流程

1:获取当前线程,然后获得当前线程ThreadLocalMap成员.赋值给map.

2:如果map不为空,以当前的ThreadLocal为key获取对应的value.

3:如果value不为空的话就返回当前的值.

4:如果map为null或者entry为空就会通过调用initialValue()方法初始化钩子函数获取初始值.并设置在map中.

initialValue()方法

当前线程本地变量没有绑定值的话,这个方法用于获取初始值.源码如下.

 protected T initialValue() {
        return null;
    }
如果没有调用set方法,直接调用get方法,就会调用这个方法,但是该方法只会被调用一次.默认情况下这个方法返回为null,如果不想返回null,可以继承ThreadLcoal覆盖这个方法.
public ThreadLocal<Foo> fooThreadLocal = 
ThreadLocal.withInitial(() -> new Foo());

 ThreadLocal.withInitial()静态方法源码如下.

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

 SuppliedThreadLocal类是一个静态内部类.源码如下.

//继承了ThreadLocal重写了initialValue方法
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

        //保存钩子函数.
        private final Supplier<? extends T> supplier;
        //传入钩子函数.
        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }

        @Override
        protected T initialValue() {
            //返回钩子函数的值作为初始值
            return supplier.get();
        }
    }

ThreadLocalMap源码 

ThreadLocal的操作都是基于ThreadLcoalMap展开的,而ThreadLocalMap是ThreadLocal的一个内部类.
ThreadLocalMap的主要成员变量
        //容量.
        private static final int INITIAL_CAPACITY = 16;

        //条目数组,作为哈希表使用.
        private Entry[] table;

        //map的条目数量
        private int size = 0;

        //扩容因子.
        private int threshold; // Default to 0


         static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

ThreadLocal中的get set remove方法都涉及到了ThreadLocalMap方法的调用.调用了如下方法,

1:set(ThreadLocal<?> key, Object value)

 向Map实例设置了key-value对.

2:getEntry(ThreadLocal<?> key)

从map实例获取所属的Entry.

3:remove(ThreadLocal<?> key)

根据key从map中移除实例所属的Entry.

ThreadLocalMap的set方法的分析.代码如下
  private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            //根据ket的hashcode值,获取对应的哈希表索引.
            int i = key.threadLocalHashCode & (len-1);

            //从槽点i位置开始向后循环搜索空的槽点,找空余槽点或者现有槽点.
            //如果没有现有槽点,必定有空余槽点,因为会扩容.
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //找到现有槽点,判断key是否和当前ThreadLcoal实例相等.
                if (k == key) {
                    e.value = value;
                    return;
                }
                //找到异常槽点,直接GC进行回收,重新设置值.

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            //如果没有发现新的槽点,增加新的Entry.
            tab[i] = new Entry(key, value);
            //设置ThreadLocal数量.
            int sz = ++size;
            //清理key为null的无效Entry.根据现在的数量和扩容因子判断是否要进行扩容.
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

好多东西都是纸上得来终觉浅,绝知此事要躬行.还是要多点点看看.多去理解.一遍不懂,那就多点几遍.

Entry的key要使用弱引用 

Entry用于保存ThreadLocalMap的key-value的条目.key使用了对ThreadLocal进行弱引用包装的实例作为key.代码如下.

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
为什么key要用弱引用呢.当一个线程被回收的话,对应的成员变量对ThreadLcoal引用也会被回收.但是ThreadLcoalMap里,存的是对ThreadLcoal的强引用.就会导致线程虽然被回收了,但是ThreadLocal还是没法回收,就会导致本地变量的值造成内存的泄漏.弱引用呢,仅有弱引用指向的对象只能过存活到下一次垃圾回收之前.换句话说,当GC发生时无论内存够不够,仅有弱引用指向的对象都会被回收,而拥有强引用的对象则不能被回收.

请抬头看看星空,有星星再看着你.

如果大家喜欢我的分享话,可以关注下微信公众号.

心有九月星辰.

标签:map,ThreadLocalMap,value,ThreadLocal,线程,key,原理
From: https://blog.csdn.net/m0_68082638/article/details/140912560

相关文章

  • 套接字编程之socket的原理
    所谓套接字,其实就是socketsocket是干嘛用的呢?当我们写一个C/S架构的软件时,是需要实现客户端与服务端之间的网络通信的,不然你的客户端怎么和服务端建立连接呢?这个socket就是负责干这个事的。还记得OSI七层协议吗?如果是计算机科班出身的同学一定学过这个,没关系,哥带你回顾下到底什......
  • 1388、STM32单片机心率(脉搏)MAX30102血氧体温检测阈值报警无线蓝牙远程(程序+原理图+
    毕设帮助、开题指导、技术解答(有偿)见文未 目录方案选择单片机的选择显示器选择方案一、设计功能二、实物图三、原理图四、程序源码五、PCB图六、proteus仿真程序流程图:原理图文字讲解:参考论文:资料包括:需要完整的资料可以点击下面的名片加下我,找我要资源压缩......
  • 1386、STM32单片机心率(脉搏)体温检测阈值设置报警无线蓝牙远程设计(程序+原理图+PCB
    毕设帮助、开题指导、技术解答(有偿)见文未 目录方案选择单片机的选择显示器选择方案一、设计功能二、实物图三、原理图四、程序源码五、PCB图六、proteus仿真程序流程图:原理图文字讲解:参考论文:资料包括:需要完整的资料可以点击下面的名片加下我,找我要资源压缩......
  • 基于OpenCV C++的网络实时视频流传输——Windows下使用TCP/IP编程原理
    1.TCP/IP编程1.1概念IP是英文InternetProtocol(网络之间互连的协议)的缩写,也就是为计算机网络相互连接进行通信而设计的协议。任一系统,只要遵守IP协议就可以与因特网互连互通。所谓IP地址就是给每个遵循tcp/ip协议连接在Internet上的主机分配的一个32bit地址。按照TC......
  • 【Redis 进阶】哨兵 Sentinel(重点理解流程和原理)
    Redis的主从复制模式下,一旦主节点由于故障不能提供服务,需要人工进行主从切换,同时大量的客户端需要被通知切换到新的主节点上,对于上了一定规模的应用来说,这种方案是无法接受的,于是Redis从2.8开始提供了RedisSentinel(哨兵)加个来解决这个问题。一、基本概念由于对Red......
  • 一文讲清楚synchronized原理---每周一更系列
    synchronized是Java提供的原子性内置锁,这种内置的并且使用者看不到的锁也被称为监视器锁。synchronized通过在代码块前后加上monitorenter和monitorexit字节码指令用于实现进入和退出。如果是同步方法,则是打上标记,隐式的使用monitorenter和monitorexit字节码指令。在jdk1.5之前......
  • react、vue组件编译区别&template解析原理
    react、vue组件打包编译为js时的区别1.react组件打包为js后,jsx会被编译为React.createElement.比如:antd的button.js(函数式组件直接returnjsx)constInternalButton=(props,ref)=>{//React.createElement第三个参数children一般兼容传数组和分开多个参数传递俩种形式......
  • 自动控制原理(Study)
    一、总体要求要求深刻领会控制系统的基本原理,掌握单输入单输出、线性定常连续控制系统的常用分析与综合方法。能够建立线性定常控制系统的数学模型,对简单的线性定常系统能够分别采用时域分析法、频率响应法和根轨迹法进行分析与综合。能够进行采样控制系统的建模和性能分析。......
  • 粉丝福利【私钥碰撞器】免费资源及原理解释
    软件置于文章最后,需要者拉到最后↓  重点:永久有效,免费脚本自研测试用下面介绍下私钥碰撞器 一:市场种类 市面上流行的碰撞软件功能主要分为两种,本地版和联网版。本地版碰撞,由于不联网相对安全点。运行速度比较快。。。联网版碰撞要运行网络节点,安全性也比较低,容易留下后......
  • 硬件开发笔记(二十九):TPS54331电源设计(二):12V转3.3V和12V转4V原理图设计
    若该文为原创文章,转载请注明原文出处本文章博客地址:https://hpzwl.blog.csdn.net/article/details/140868749长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中…硬件相关开发......