private static
类型的,用于关联线程和线程上下文总结:
ThreadLocal的常用方法
方法声明 | 描述 |
---|---|
ThreadLocal() | 创建ThreadLocal对象 |
public void set( T value) | 设置当前线程绑定的局部变量 |
public T get() | 获取当前线程绑定的局部变量 |
public void remove() | 移除当前线程绑定的局部变量 |
示例
public class MyDemo {
// ThreadLocal本地线程变量
private ThreadLocal<String> tl = new ThreadLocal<>();
// 普通对象变量
private String content;
public static void main(String[] args) {
MyDemo demo = new MyDemo();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(() -> {
String data = Thread.currentThread().getName() + "的数据";
// 普通变量方式:
demo.content = data;
System.out.print("");
System.out.println("普通变量获取到数据: " + Thread.currentThread().getName() + "--->" + demo.content);
// ThreadLocal方式:
// demo.tl.set(data);
// System.out.print("");
// System.out.println("ThreadLocal获取到数据: " + Thread.currentThread().getName() + "--->" + demo.tl.get());
});
thread.setName("线程" + i);
thread.start();
}
}
}
分别执行查看两种方式的结果
普通变量方式:
ThreadLocal方式:
synchronized | ThreadLocal | |
---|---|---|
原理 | 同步机制采用’以时间换空间’的方式, 只提供了一份变量,让不同的线程排队访问 | ThreadLocal采用’以空间换时间’的方式, 为每一个线程都提供了一份变量的副本,从而实现同时访问 而相不干扰 |
侧重点 | 多个线程之间访问资源的同步 | 多线程中让每个线程之间的数据相互隔离 |
ThreadLocal
都创建一个Map
Map
的key
Map
的value
JDK最早期的ThreadLocal
确实是这样设计的,但现在早已不是了
ThreadLocal.ThreadLocalMap
的实例变量threadLocals
,也就是说每个线程有一个自己的ThreadLocalMap实际上key并不是ThreadLocal本身,而是它的一个弱引用
)线程隔离
弱引用类型
ThreadLocal设计改良的好处
Map
存储的Entry
数量就会变少
Thread
的数量决定(每个Thread作为key)ThreadLocal
的数量决定(ThreadLocal作为线程内map的key)Thread
销毁之后,对应的ThreadLocalMap
也会随之销毁,能减少内存的使用不再会被使用的对象或者变量引用的内存不能被回收,就是内存泄露
为什么要用弱引用?
public void function01(){
ThreadLocal tl = new ThreadLocal<Integer>();
tl.set(2021);
tl.get();
}
强引用
指向ThreadLocal对象弱引用
指向ThreadLocal对象value置为null
的remove
,其实就不用走以上步骤了除了构造方法之外, ThreadLocal对外暴露的方法有以下4个:
方法声明 | 描述 |
---|---|
protected T initialValue() | 返回当前线程局部变量的初始值 |
public void set( T value) | 设置当前线程绑定的局部变量 |
public T get() | 获取当前线程绑定的局部变量 |
public void remove() | 移除当前线程绑定的局部变量 |
// 设置当前线程对应的ThreadLocal的值
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断map是否存在
if (map != null){
// 存在则调用map.set设置此实体entry
map.set(this, value);
} else {
// 1)当前线程Thread 不存在ThreadLocalMap对象
// 2)则调用createMap进行ThreadLocalMap对象的初始化
// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
createMap(t, value);
}
}
/**
* 获取当前线程Thread对应维护的ThreadLocalMap
* ThreadLocal.ThreadLocalMap threadLocals = null;
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 创建当前线程Thread对应维护的ThreadLocalMap
void createMap(Thread t, T firstValue) {
//这里的this是调用此方法的threadLocal
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 返回当前线程中保存ThreadLocal的值
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 如果此map存在
if (map != null) {
// 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
ThreadLocalMap.Entry e = map.getEntry(this);
// 对e进行判空
if (e != null) {
// 获取存储实体 e 对应的 value值
// 即为我们想要的当前线程对应此ThreadLocal的值
T result = (T)e.value;
return result;
}
}
/*
初始化 : 有两种情况有执行当前代码
第一种情况: map不存在,表示此线程没有维护的ThreadLocalMap对象
第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry
*/
return setInitialValue();
}
// 初始化
private T setInitialValue() {
// 调用initialValue获取初始化的值
// 此方法可以被子类重写, 如果不重写默认返回null
T value = initialValue();
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断map是否存在
if (map != null){
// 存在则调用map.set设置此实体entry
map.set(this, value);
} else{
// 1)当前线程Thread 不存在ThreadLocalMap对象
// 2)则调用createMap进行ThreadLocalMap对象的初始化
// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
createMap(t, value);
}
// 返回设置的值value
return value;
}
/**
* 返回当前线程对应的ThreadLocal的初始值
* 此方法的第一次调用发生在,当线程通过get方法访问此线程的ThreadLocal值时
* 除非线程先调用了set方法,在这种情况下,initialValue 才不会被这个线程调用。
* 通常情况下,每个线程最多调用一次这个方法。
*
* 这个方法仅仅简单的返回null {@code null};
* 如果程序员想ThreadLocal线程局部变量有一个除null以外的初始值,
* 必须通过子类继承{@code ThreadLocal} 的方式去重写此方法
* 通常, 可以通过匿名内部类的方式实现
*
* @return 当前ThreadLocal的初始值
*/
protected T initialValue() {
return null;
}
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
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();
}
}
示例:
@Test
public void jdk8Test(){
// 方式一:
Supplier<String> supplier =new Supplier<String>(){
@Override
public String get(){
return"supplier_new";
}
};
threadLocal= ThreadLocal.withInitial(supplier);
System.out.println(threadLocal.get());// supplier_new
// 方式二:
threadLocal= ThreadLocal.withInitial(()->"sup_new_2");
System.out.println(threadLocal.get());// sup_new_2
}
// 删除当前线程中保存的ThreadLocal对应的实体entry
public void remove() {
// 获取当前线程对象中维护的ThreadLocalMap对象
ThreadLocalMap m = getMap(Thread.currentThread());
// 如果此map存在
if (m != null){
// 存在则调用map.remove
// 以当前ThreadLocal为key删除对应的实体entry
m.remove(this);
}
}
异步场景
下是无法给子线程共享父线程中创建的线程副本数据的InheritableThreadLocal
类public class InheritableThreadLocalDemo {
public static void main(String[] args) {
ThreadLocal<String> ThreadLocal = new ThreadLocal<>();
ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
ThreadLocal.set("父类数据:threadLocal");
inheritableThreadLocal.set("父类数据:inheritableThreadLocal");
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程获取父类ThreadLocal数据:" + ThreadLocal.get());
System.out.println("子线程获取父类inheritableThreadLocal数据:" + inheritableThreadLocal.get());
}
}).start();
}
}
输出结果:
子线程获取父类ThreadLocal数据:null
子线程获取父类inheritableThreadLocal数据:父类数据:inheritableThreadLocal
原理
子线程
都可以访问它保存的值public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
// 直接获取Thread类中的map
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
// 创建map,赋值inheritableThreadLocals
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
Thread#init
方法在Thread的构造方法
中被调用private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
TransmittableThreadLocal
组件就可以解决这个问题散列表数组冲突
问题int i = key.threadLocalHashCode & (len-1);
HASH_INCREMENT = 0x61c88647
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
static class ThreadLocalMap {
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
}
斐波那契数
也叫黄金分割数
测试:
线性探测法(不断加 1)
private void set(ThreadLocal<?> key, Object value) {
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
//计算索引(重点代码,刚才分析过了)
int i = key.threadLocalHashCode & (len-1);
/**
* 使用线性探测法查找元素(重点代码)
*/
for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//ThreadLocal 对应的 key 存在,直接覆盖之前的值
if (k == key) {
e.value = value;
return;
}
// key为 null,但是值不为 null,说明之前的 ThreadLocal 对象已经被回收了,
// 当前数组中的 Entry 是一个陈旧(stale)的元素
if (k == null) {
//用新元素替换陈旧的元素,这个方法进行了不少的垃圾清理动作,防止内存泄漏
replaceStaleEntry(key, value, i);
return;
}
}
//ThreadLocal对应的key不存在并且没有找到陈旧的元素,则在空元素的位置创建一个新的Entry。
tab[i] = new Entry(key, value);
int sz = ++size;
/**
* cleanSomeSlots用于清除那些e.get()==null的元素,
* 这种数据key关联的对象已经被回收,所以这个Entry(table[index])可以被置null。
* 如果没有清除任何entry,并且当前使用量达到了负载因子所定义(长度的2/3),那么进行 * rehash(执行一次全表的扫描清理工作)
*/
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* Increment i modulo len.
* 获取数组的下一个索引
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
线程间隔离
且在方法间共享
的场景回收键头null的Entry