我们在介绍threadLocal子类及实现原理之前,先介绍一下threadLocal类。
ThreadLocal类简介
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
ThreadLocal原理
ThreadLocal 是为了解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal 并不是一个Thread,而是 Thread 的局部变量。
ThreadLocal 用于保存某个线程共享变量:对于同一个 static ThreadLocal,其为每个使用该变量的线程提供独立的变量副本,不同线程只能从中 get、set 和 remove 自己的变量,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就像是线程的本地变量。
ThreadLocal使用场景(ThreadLocal的应用场景 - sw_kong - 博客园)
这种场景通常用于保存线程不安全的工具类,典型的需要使用的类就是 SimpleDateFormat。
SimpleDateFormat对象是非线程安全的,如果多个线程需要使用,就必须每个线程创建一个SimpleDateFormat对象来确保线程安全,这样会增大内存开销。
ThreadLocal用法
void set(Object value)设置当前线程的线程局部变量的值。
public Object get()返回当前线程所对应的线程局部变量。
public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue()返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object) 时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一个 null。
ThreadLocal源码分析(ThreadLocal - 简书)
主要方法:
set()方法:
public void set(T value) {
//(1)获取当前线程(调用者线程)
Thread t = Thread.currentThread();
//(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
//(3)如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
if (map != null)
map.set(this, value);
//(4)如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
//获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
从threadlocal的set方法我们可以看到,首先获取到了当前线程t,然后调用getMap获取ThreadLocalMap,如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去。如果该Map不存在,则初始化一个。
从上面代码可以看出每个线程持有一个ThreadLocalMap对象。每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。
ThreadLocalMap
通过上面的代码不难看出在实例化ThreadLocalMap时创建了一个长度为16的Entry数组。通过hashCode与length位运算确定出一个索引值i,这个i就是被存储在table数组中的位置。
每个线程Thread持有一个ThreadLocalMap类型的实例threadLocals,结合此处的构造方法可以理解成每个线程Thread都持有一个Entry型的数组table,而一切的读取过程都是通过操作这个数组table完成的。
对于一个Thread来说,只会持有一个ThreadLocalMap,所以A、B、C对应同一个ThreadLocalMap对象。为了管理A、B、C,于是将他们存储在一个数组的不同位置,而这个数组就是上面提到的Entry型的数组table。
get()方法:
public T get() {
//(1)获取当前线程
Thread t = Thread.currentThread();
//(2)获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
//(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
return setInitialValue();
}
ThreadLocalMap中getEntry()方法:
setInitialValue()方法:
private T setInitialValue() {
//protected T initialValue() {return null;}
T value = initialValue();
//获取当前线程
Thread t = Thread.currentThread();
//以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
//如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
if (map != null)
map.set(this, value);
//如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
return value;
}
remove()方法:
remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量
public void remove() {
//获取当前线程绑定的threadLocals
ThreadLocalMap m = getMap(Thread.currentThread());
//如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
if (m != null)
m.remove(this);
}
ThreadLocal不支持继承性
同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals中为当前调用线程对应的本地变量,所以二者自然是不能共享的)。
package com.redistext.stream;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
/**
* @author: maorui
* @description: TODO
* @date: 2021/11/6 10:16
* @description: V1.0
*/
public class StreamTest {
//(1)创建ThreadLocal变量
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
//在main线程中添加main线程的本地变量
threadLocal.set("mainVal");
//新创建一个子线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程中的本地变量值:"+threadLocal.get());
}
});
thread.start();
//输出main线程中的本地变量值
System.out.println("main线程中的本地变量值:"+threadLocal.get());
}
}
结果:
mainx线程中的本地变量值:mainVal
子线程中的本地变量值:null
ThreadLocal类是不能提供子线程访问父线程的本地变量的,而InheritableThreadLocal类则可以做到这个功能。
ThreadLocal内存泄漏问题(没看太明白):
Java中的ThreadLocal详解 - 夏末秋涼 - 博客园
ThreadLocal的内存泄露?什么原因?如何避免? - 知乎
参考文档: