深入解析ThreadLocal

1 ThreadLocal是什么

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID)

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

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

ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
  • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

ThreadLocal 有两大作用:

  • 线程间:线程隔离,避免争用引发线程安全问题
  • 线程内:实现了线程内资源共享

2 ThreadLocal基本方法

方法声明描述
ThreadLocal()创建ThreadLocal对象
public void set( T value)设置当前线程绑定的局部变量
public T get()获取当前线程绑定的局部变量
public void remove()移除当前线程绑定的局部变量
protected T initialValue()返回当前线程的这个线程局部变量的初始值

简单使用:

public class ThreadTest1 {
    static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    static int nomalVar = 1;

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                String tname = Thread.currentThread().getName();
                threadLocal.set("1");
                System.out.println(String.format("线程%s设置threadLocal值:1",tname));
                System.out.println(tname+"操作共享全局变量"+ nomalVar);
                nomalVar++;
                printTName();
            }finally {
                threadLocal.remove();
            }

        },"线程1").start();

        new Thread(() -> {
            try {
                String tname = Thread.currentThread().getName();
                threadLocal.set("2");
                System.out.println(String.format("线程%s设置threadLocal值:2",tname));
                System.out.println(tname+"操作共享全局变量"+ nomalVar);
                nomalVar++;
                printTName();
            }finally {
                threadLocal.remove();
            }

        },"线程2").start();
    }

    private static void printTName() {
        String tname = Thread.currentThread().getName();
        // 得到存放在 threadLoca 中的值
        String result = threadLocal.get();
        System.out.println(String.format("线程%s 取得:%s",
                tname, result));
    }
}

initialValue()方法的使用:

public class ThreadTest2 {
    static ThreadLocal<String> threadLocal = new ThreadLocal(){
        @Override
        protected Object initialValue() {
            System.out.println("执行了初始化方法");
            return "trq";
        }
    };

    public static void main(String[] args) {
        String result = threadLocal.get();	//initialValue与get可以一起操作,实现正常存取
        System.out.println("读取到的内容是:"+result);
    }
}

在这里插入图片描述

首先来分析一下set()方法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,ThreadLocalMap底层是一个数组,数组中元素类型是Entry类型。

set操作是向当前线程的ThreadLocal.ThreadLocalMap类型的成员变量threadLocals中设置值,keythisvalue是指定的值(注意,这里传的this代表的是那个ThreadLocal类型的对象)

也就是说,每个线程都维护了一个ThreadLocal.ThreadLocalMap类型的对象,而set操作其实就是以ThreadLocal变量为key,指定的值为value,最后将这个键值对封装成Entry对象放到该线程的ThreadLocal.ThreadLocalMap对象中。每个ThreadLocal变量在该线程中都是ThreadLocal.ThreadLocalMap对象中的一个Entry

再来看get()方法:
在这里插入图片描述
get()方法就是从当前线程的ThreadLocal.ThreadLocalMap对象中取出对应的ThreadLocal变量所对应的值:

  1. 首先获取当前线程, 根据当前线程获取一个Map
  2. 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的Entry e
  3. Map为空或者e为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKeyfirstValue创建一个新的Map

同理,remove()方法就是清除这个值。
在这里插入图片描述

3 ThreadLocalMap对象是什么

ThreadLocalMapThreadLocal 的静态内部类,当一个线程有多个 ThreadLocal 时,需要一个容器来管理多个 ThreadLocalThreadLocalMap 的作用就是管理线程中多个 ThreadLocal

  • ThreadLocalMap的Entry实现继承了WeakReference<ThreadLocal<?>>
//键值对实体的存储结构
static class Entry extends WeakReference<ThreadLocal<?>> {
	//当前线程关联的value
    Object value;

    //构造键值对
    //Entry中的key只能是ThreadLocal对象	v作为值
    //Entry继承WeakReference,也就是key(ThreadLocal)是弱引用,其目的是将ThreadLocal对象的生命周期和线程生命周期解绑。
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
//初始容量,必须是2的幂。
private static final int INITIAL_CAPACITY = 16;
//存储ThreadLocal的键值对实体数组
private Entry[] table;
//ThreadLocalMap元素的数量(每个元素都是一个Entry)
private int size = 0;
  • Entry key 就是 ThreadLocal 的引用,value ThreadLocal 的值

Java中的引用有4种类型: 强、软、弱、虚,当前这个问题主要涉及到强引用弱引用

强引用:我们平时一般都是这种引用,当一个对象被一个或一个以上的引用变量所引用时,它处于可达状态,不可能被系统垃圾回收机制回收。
弱引用:弱引用通过WeakReference类实现。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。当然,并不是说当一个对象只有弱引用时,它就会被立即回收,而是必须等到系统垃圾回收机制运行时才会被回收。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

简单理解就是当垃圾回收时,该对象只被WeakReference对象的弱引用字段(T reference)所引用,而未被任何强类型的对象引用,那么,该弱引用的对象就会被回收。

在这里插入图片描述
要了解ThreadLocalMap的实现, 我们先从往该Map中添加一个值开始:

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;	//存储ThreadLocal的键值对实体数组
    int len = tab.length;	//数组长度
    int i = key.threadLocalHashCode & (len-1);	//获取传入key(threadLocal)的索引位置

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {	//判断entry里的key是否和传入的key是同一个对象
            e.value = value;	//如果是 则把传入的value赋值给entry中的
            return;
        }

        if (k == null) {//ThreadLocal已经被回收了
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    //如果清理完无用条目(ThreadLocal被回收的条目)、并且数组中的数据大小 > 阈值的时候对当前的Table进行重新哈希
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

Get()方法:

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);//找到ThreadLocal的索引位置
    Entry e = table[i];//获取该位置的entry元素
    if (e != null && e.get() == key)//判断entry不为空 且键与传入的threadLocal是同一个对象
        return e;//返回该元素
    else
        return getEntryAfterMiss(key, i, e);//去后面的索引继续查找
}

在这里插入图片描述
首先,再执行第一行代码static ThreadLocal<Person> tl = new ThreadLocal<>();时,栈内存和堆内存情况如下:
在这里插入图片描述
在这里插入图片描述

4 ThreadLocal使用场景

ThreadLocal 适用于如下场景

  • 存储需要在线程隔离的数据
  • 跨层参数传递

假如在我们的业务方法中需要调用其他方法,同时其他方法都需要用到同一个对象时,可以使用ThreadLocal替代参数的传递(耦合高)或者static静态全局变量(多线程不安全)。使用ThreadLocal后,在第一层把变量值保存到ThreadLocal中,在使用的层次方法中直接从ThreadLocal中取出。

例如在Spring的@Transaction事务声明的注解中就使用ThreadLocal保存了当前的Connection对象,避免在本次调用的不同方法中使用不同的Connection对象。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值