java并发编程(一)ThreadLocal
使用InheritableThreadLocal创建实例的话,子线程能输出主线程的值,但是如果使用ThreadLocal创建实例的话,子线程不能输出主线程的值。ThreadLocalMap是一个定制化的Hashmap,他的键是ThreadLocal 实例,值是你set后面的值。第一个线程remove之后再get,输出的是null,说明第二个线程设置的值访问不到,不是共享的。为thread 线程,而
一、ThreadLocal使用案例
ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。
package cn.tx.threadlocal;
public class ThreadLocalTest {
// (1)print函数
static void print(String str) {
//1.1打印当前线程本地内存中localVariable变量的值
System.out.println(str + ":" + localVariable.get());
//1.2清除当前线程本地内存中的localVariable变量
//localVariable. remove () ;
}
// (2)创建ThreadLocal变量, 用于存取值的
static ThreadLocal<String> localVariable = new ThreadLocal<>();
public static void main(String[] args) {
//(3) 创建线程one
Thread threadOne = new Thread(new Runnable() {
public void run() {
//3.1设置线程One中本地变量localVariable的值
localVariable.set("threadOne local variable");
//3.2调用打印函数
print("threadOne");
//3.3打印本地变量值
System.out.println("threadOne remove after" + ":" + localVariable.get());
}
});
// (4) 创建线程two
Thread threadTwo = new Thread(new Runnable() {
public void run() {
//4.1 设置线程Two中本地变量localVariable的值
localVariable.set("threadTwo local variable");
//4.2调用打印函数
print("threadTwo");
//4.3打印本地变量值
System.out.println("threadTwo remove after" + ":" + localVariable.get());
}
});
//(5)启动线程
threadOne.start();
threadTwo.start();
}
}
当1.2的代码注释掉之后,输出结果如下:
虽然两个线程都调用set方法,在localVariable里面设置了值,但是最后输出的时候还是自己输出自己的值,没有共享。
当1.2的代码打开之后,输出结果如下:
第一个线程remove之后再get,输出的是null,说明第二个线程设置的值访问不到,不是共享的。
二、ThreadLocal的原理
1.首先明确几个概念
(1)Thread:线程实例的「数据载体」
每个线程实例内部持有两个 ThreadLocalMap,真正存储线程私有数据的地方就是这两个 Map:
- threadLocals:当前线程的私有 ThreadLocalMap → 存储该线程的 ThreadLocal 数据,默认 null,第一次调用 ThreadLocal.set()/get() 时才会创建。
- inheritableThreadLocals:可继承的 ThreadLocalMap → 用于子线程继承父线程的 ThreadLocal 数据(比如父线程设置的值,子线程能直接拿到)。
(2)ThreadLocalMap
ThreadLocalMap是一个定制化的Hashmap,他的键是ThreadLocal 实例,值是你set后面的值。
由于HashMap的键唯一,单个 ThreadLocal 实例(Key)在单个线程的 ThreadLocalMap 中,只能对应 1 个 Value。
(3)ThreadLocal:线程私有数据的「操作工具」
它是一个泛型类,本身不存储数据,只负责操作线程的 ThreadLocalMap:
- +set(value: T): void:公开方法 → 给当前线程设置 ThreadLocal 私有数据。
- +get(): T:公开方法 → 获取当前线程的 ThreadLocal 私有数据。
- +remove(): void:公开方法 → 移除当前线程的 ThreadLocal 数据(避免内存泄漏)
- #getMap(t: Thread): ThreadLocalMap:包访问方法 → 拿到指定线程的 ThreadLocalMap。
- #createMap(t: Thread, firstValue: T): void:包访问方法 → 为线程创建 ThreadLocalMap,并存入初始值。
-setInitialValue(): T:私有方法 → 当调用 get() 但未设置值时,生成初始值(默认返回 null,可重写)。
2.下面具体分析ThreadLocal的原理
2.1set方法
public void setValue(T vaue){
//(1)获取当前线程
Thread t = Thread. currentThread() ;
// (2)将当前线程作为key,去查找对应的线程变量,找到则设置
ThreadLocalMap map = getMap(t) ;
if (map != null)
map.set (this, value) ;
else
//(3)第一次调用就创建当前线程对应的HashMap
createMap(t, value) ;
}
以调用 ThreadLocal.set(“张三的手机号”) 为例:
- ThreadLocal.set() 先通过 Thread.currentThread() 拿到当前线程(Thread 实例)。
- 调用getMap(t) 获取该线程的 threadLocals(ThreadLocalMap)。
- 如果 ThreadLocalMap 已存在→ 以当前 ThreadLocal 为 Key,把值存入 Map;
- 如果 ThreadLocalMap 不存在 → 调用createMap(t, value) 创建新的 ThreadLocalMap,并赋值给线程的 threadLocals。
2.2get方法
public T get() {
//(4) 获取当前线程
Thread t = Thread. currentThread() ;
// (5)获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t) ;
// (6)如果threadLocals不为null,则返回对应本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map. getEntry(this) ;
if (e != null) {
@SuppressWarnings ("unchecked" )
T result = (T)e.value;
return result;}}
// (7) threadLocals为空则初始化当前线程的threadLocals成员变量
return setInitialValue() ;
}
2.3remove方法
会删除当前线程的threadLocals中的本地变量。
public void remove () {
ThreadLocalMap m = getMap (Thread. currentThread());
if (m != null)
m. remove (this) ;
}
3.ThreadLocal不支持继承性
同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。
根据上节的介绍,这应该是正常现象,因为在子线程thread里面调用get方法时当前线程
为thread 线程,而这里调用set 方法设置线程变量的是main线程,两者是不同的线程,自
然子线程访问时返回null。那么有没有办法让子线程能访问到父线程中的值?答案是有。InheritableThreadLocal 应运而生。
三、InheritableThreadlocal类
public class ThreadLocalTest1 {
//(1)创建线程变量
public static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();
public static void main(String[] args) {
//(2) 设置线程变量
threadLocal.set("hello world");
//(3) 启动子线程
Thread thread = new Thread(new Runnable() {
public void run() {
//(4) 子线程输出线程变量的值
System.out.println("thread:" + threadLocal.get());
}
});
thread.start();
//(5). 主线程输出线程变量的值
System.out.println("main:" + threadLocal.get());
}
}
使用InheritableThreadLocal创建实例的话,子线程能输出主线程的值,但是如果使用ThreadLocal创建实例的话,子线程不能输出主线程的值。
那这是为什么呢?
InheritableThreadLocal是继承自ThreadLocal的,重写了下面三个方法
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
// 1. 重写:父线程值传递给子线程时的转换逻辑(默认直接返回父线程值,可重写定制)
protected T childValue(T parentValue) {
return parentValue;
}
// 2. 重写:获取线程的 inheritableThreadLocals 而非 threadLocals
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
// 3. 重写:创建 Map 时赋值给 inheritableThreadLocals 而非 threadLocals
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
下面是Thread构造器当中的代码片段
- 父线程设置值:父线程通过 InheritableThreadLocal.set() 把值存入自己的inheritableThreadLocals(而非 threadLocals)。
- 子线程创建触发复制:父线程新建子线程时,Thread构造器会调用 init 方法,检查父线程的 inheritableThreadLocals 是否存在。
- 复制 Map到子线程:如果存在,调用 ThreadLocal.createInheritedMap() 复制父线程的 inheritableThreadLocals,生成子线程自己的inheritableThreadLocals(浅拷贝,值为引用类型时父子线程共享对象)。
- 子线程获取继承的值:子线程通过InheritableThreadLocal.get() 从自己的 inheritableThreadLocals中获取父线程传递过来的值。
更多推荐

所有评论(0)