ThreadLocal变量随笔

一、ThreadLocal的使用

ThreadLocal 类接口很简单,只有 4 个方法,我们先来了解一下:
• void set(Object value)
设置当前线程的线程局部变量的值。
• public Object get()
该方法返回当前线程所对应的线程局部变量。
• public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK
5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动
被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它
可以加快内存回收的速度。
• protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为
了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get()
或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一
个 null。
线程类

/**
 * 构建线程类
 */
public class ThreadInheritTest extends Thread{

   private   ThreadLocal<String> threadLocal;
   private   StringBuilder stringBuilder;

    public ThreadInheritTest(ThreadLocal<String> threadLocal, StringBuilder stringBuilder){
        this.threadLocal = threadLocal;
        // ***这里有个问题??会发现在子线程里没有值***
        threadLocal.set("本地变量");

        this.stringBuilder = stringBuilder;
    }
    @Override
    public void run() {
        // 线程本地变量
        threadLocal.set(Thread.currentThread().getName());
        // 线程共享变量
        this.stringBuilder = this.stringBuilder.append(Thread.currentThread().getName());
        // 只有当前线程的信息
        System.out.println("ThreadLocal变量"+Thread.currentThread().getName()+":"+threadLocal.get());
        System.out.println("----------------------------------------------------------------");
        // 会把所有线程的结果都拼接上
        System.out.println("普通变量"+Thread.currentThread().getName()+":"+this.stringBuilder);
    }
}

在主线程中,使用ThreadLocal变量设置值时,会发现在子线程里没有值?
测试类

public class ThreadLocalMapTest {
    public static ThreadLocal<String> local = new ThreadLocal<>();
    public static StringBuilder builder = new StringBuilder("普通的变量");
    public static void main(String[] args) {
        for(int i=0;i<20;i++){
            ThreadInheritTest thread = new ThreadInheritTest(local,builder);
            thread .setName("第"+i+"个线程");
            thread.start();
        }

    }

}

从运行结果可以看出,会发现同一个ThreadLocal变量不会线程共享

ThreadLocal变量第0个线程:第0个线程
----------------------------------------------------------------
普通变量第0个线程:普通的变量第0个线程
ThreadLocal变量第4个线程:第4个线程
----------------------------------------------------------------
普通变量第4个线程:普通的变量第0个线程第4个线程
ThreadLocal变量第5个线程:第5个线程
----------------------------------------------------------------
普通变量第5个线程:普通的变量第0个线程第4个线程第5个线程
ThreadLocal变量第2个线程:第2个线程
----------------------------------------------------------------
普通变量第2个线程:普通的变量第0个线程第4个线程第5个线程第2个线程
ThreadLocal变量第8个线程:第8个线程
----------------------------------------------------------------
普通变量第8个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程
ThreadLocal变量第1个线程:第1个线程
----------------------------------------------------------------
普通变量第1个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程
ThreadLocal变量第10个线程:第10个线程
----------------------------------------------------------------
普通变量第10个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程
ThreadLocal变量第6个线程:第6个线程
----------------------------------------------------------------
普通变量第6个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程
ThreadLocal变量第11个线程:第11个线程
----------------------------------------------------------------
普通变量第11个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程
ThreadLocal变量第17个线程:第17个线程
----------------------------------------------------------------
普通变量第17个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程
ThreadLocal变量第12个线程:第12个线程
----------------------------------------------------------------
普通变量第12个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程
ThreadLocal变量第13个线程:第13个线程
----------------------------------------------------------------
普通变量第13个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程
ThreadLocal变量第3个线程:第3个线程
----------------------------------------------------------------
普通变量第3个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程
ThreadLocal变量第15个线程:第15个线程
----------------------------------------------------------------
普通变量第15个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程
ThreadLocal变量第14个线程:第14个线程
----------------------------------------------------------------
ThreadLocal变量第19个线程:第19个线程
----------------------------------------------------------------
ThreadLocal变量第9个线程:第9个线程
----------------------------------------------------------------
普通变量第14个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程第19个线程
ThreadLocal变量第7个线程:第7个线程
ThreadLocal变量第18个线程:第18个线程
----------------------------------------------------------------
普通变量第18个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程第19个线程第9个线程第16个线程第7个线程第18个线程
普通变量第9个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程第19个线程第9个线程第16个线程
普通变量第19个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程第19个线程第9个线程
ThreadLocal变量第16个线程:第16个线程
----------------------------------------------------------------
----------------------------------------------------------------
普通变量第16个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程第19个线程第9个线程第16个线程第7个线程第18个线程
普通变量第7个线程:普通的变量第0个线程第4个线程第5个线程第2个线程第8个线程第1个线程第10个线程第6个线程第11个线程第17个线程第12个线程第13个线程第3个线程第14个线程第15个线程第19个线程第9个线程第16个线程第7个线程第18个线程

ThreadLocal原理

ThreadLocal的线程独享变量是通过ThreadLocalMap的存储结构类实现的
以共享变量ThreadLocal的实例对象为Key,设置的实例对象为Value的Map的机构。
ThreadLocal的set设置过程

Created with Raphaël 2.3.0 设置值threadLocal.set(val) 获取当前线程 Thread t = Thread.currentThread(); 获取当前线程的ThreadLocalMap t.threadLocals 将ThreadLocal当作Key,需要设置的值为Value放到Map中 实现线程独享变量

ThreadLocalMap的类是定义在ThreadLocal里面的内部类,下面是源码:

在这里插入描述

但是使用的变量定义却是在在Thread中:
在这里插入图片描述

ThreadLocalMap的使用过程分析

  1. 初始化
    ThreadLocalMap是数组存储结构
    当我们在线程中,第一次给ThreadLocal变量负值时,就会触发threadLocals的初始化。
    源码:
    /**
     * 设置
     */
    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
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

初始化容量和HashMap默认一样是 16
注意:这里的扩容实在三分之二处

 private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
  1. 放置值

通过hash算法找到数组的指定位置

 private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            // hash计算 确定数组位置
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                //为了解决hash冲突
                 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)
                rehash();
        }

在设置值的过程涉及到hash碰撞的解决方法——使用开放地址法(为线性探测再散列),扩容(单线程)。

ThreadLocal的问题

为什么要用ThreadLocal?
JDBC的链接告诉我们,ThreadLocal可以实现跨方法的参数传递
应用场景:日志流水TraceId链路追踪,JDBC事务链接
ThreadLoca会引发内存泄漏吗?怎么避免?
会,虽然ThreadLocalMap的Key是弱引用,当是Value是强引用。
手动调用remove方法就可以去掉这个强引用。

扩展:

hash算法碰撞的解决方案
开放定址法:
基本思想是,出现冲突后按照一定算法查找一个空位置存放,根据算法的不
同又可以分为线性探测再散列、二次探测再散列、伪随机探测再散列。
线性探测再散列即依次向后查找,二次探测再散列,即依次向前后查找,增
量为 1、2、3 的二次方,伪随机,顾名思义就是随机产生一个增量位移。

链地址法:
这种方法的基本思想是将所有哈希地址为 i 的元素构成一个称为同义词链的
单链表,并将单链表的头指针存在哈希表的第 i 个单元中,因而查找、插入和删
除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。Java
里的 HashMap 用的就是链地址法,为了避免 hash 洪水攻击,1.8 版本开始还引
入了红黑树。

再哈希法:
这种方法是同时构造多个不同的哈希函数:Hi=RH1(key) i=1,2,…,k
当哈希地址 Hi=RH1(key)发生冲突时,再计算 Hi=RH2(key)……,直到冲突
不再产生。这种方法不易产生聚集,但增加了计算时间。

建立公共溢出区
这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本
表发生冲突的元素,一律填入溢出表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值