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
中设置值,key
是this
,value
是指定的值(注意,这里传的this代表的是那个ThreadLocal类型的对象)
也就是说,每个线程都维护了一个ThreadLocal.ThreadLocalMap
类型的对象,而set
操作其实就是以ThreadLocal
变量为key
,指定的值为value
,最后将这个键值对封装成Entry
对象放到该线程的ThreadLocal.ThreadLocalMap
对象中。每个ThreadLocal
变量在该线程中都是ThreadLocal.ThreadLocalMap
对象中的一个Entry
。
再来看get()
方法:
get()
方法就是从当前线程的ThreadLocal.ThreadLocalMap对象中取出对应的ThreadLocal
变量所对应的值:
- 首先获取当前线程, 根据当前线程获取一个Map
- 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的Entry e
- Map为空或者e为空,则通过
initialValue
函数获取初始值value
,然后用ThreadLocal
的引用和value
作为firstKey
和firstValue
创建一个新的Map
同理,remove()
方法就是清除这个值。
3 ThreadLocalMap对象是什么
ThreadLocalMap
是 ThreadLocal
的静态内部类,当一个线程有多个 ThreadLocal
时,需要一个容器来管理多个 ThreadLocal
,ThreadLocalMap
的作用就是管理线程中多个 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对象。