文章目录
1.简介
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
2.每个线程的变量副本是存储在哪里的
Thread类中提供了这样两个变量可以使每个线程有属于自己本身线程的变量而不被其他线程共享
我们发现二者都是ThreadLocal
内部类ThreadLocalMap
类型的变量
3.通过源码分析ThreadLocal具体实怎样工作的
ThreadLocal提供了set和get访问器用来访问与当前线程相关联的线程局部变量,
3.1set方法
public void set(T value) {
//(1)获取当前线程(调用者线程)
Thread t = Thread.currentThread();
//(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
//(3)如果map不为null,就直接添加本地变量
if (map != null)
map.set(this, value);
//(4)如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
}
getMap
方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}
createMap方法:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
我们通过分析set方法源码:某个线程通过threadLocal变量设置值的时候会获取自己线程的threadLocals
变量即getMap(t),通过
ThreadLocalMap
类型来接收,并且通过set(this,value)将threadLocal作为键,value当作值存储在本地线程,到现在为止我们就可以得出下面的结论了:
- 每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面
3.2get方法
我们接着来看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();
}
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;
}
在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,以threadLocal作为参数,取出value,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null。
4.变量副本【每个线程中保存的那个map中的变量】是怎么声明和初始化的
从我们分析set和get方法的时候其实已经得到了这个答案,当线程中的threadLocals
成员是null的时候,会调用ThreadLocal.createMap(Thread t, T firstValue)
创建一个map。同时根据函数参数设置上初始值。也就是说,当前线程的threadlocalmap是在第一次调用set的时候创建map并且设置上相应的值的。
在每个线程中,都维护了一个threadlocals对象,在没有ThreadLocal变量的时候是null的。一旦在ThreadLocal的createMap函数中初始化之后,这个threadlocals就初始化了。以后每次ThreadLocal对象想要访问变量的时候,比如set函数和get函数,都是先通过getMap(Thread t)函数,先将线程的map取出,然后再从这个在线程(Thread)中维护的map中取出数据或者存入对应数据。
5.不同的线程局部变量,比如说声明了n个(n>=2)这样的线程局部变量threadlocal,那么在Thread中的threadlocals中是怎么存储的呢?threadlocalmap中是怎么操作的?
我们先来一个Demo:
public class ThreadLocalTest {
static ThreadLocal<String> localVar = new ThreadLocal<>();
static void print(String str) {
//打印当前线程中本地内存中本地变量的值
System.out.println(str + " :" + localVar.get());
//清除本地内存中的本地变量
localVar.remove();
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//设置线程1中本地变量的值
localVar.set("localVar1");
//调用打印方法
print("thread1");
//打印本地变量
System.out.println("after remove : " + localVar.get());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//设置线程1中本地变量的值
localVar.set("localVar2");
//调用打印方法
print("thread2");
//打印本地变量
System.out.println("after remove : " + localVar.get());
}
});
t1.start();
t2.start();
}
}
这两个线程都是通过localVar
这个ThreadLocal
来往本地线程中完成副本的创建和赋值,并且在set函数中,map.set(this, value)
把当前的localVar
传入到map中作为键,也就是说,在不同的线程的threadlocals变量中,都会有一个以你所声明的那个线程局部变量threadlocal作为键的key-value。假设说声明了N个这样的线程局部变量变量,那么在线程的ThreadLocalMap中就会有n个分别以你的线程局部变量作为key的键值对。
6.总结
在往本地线程设置线程局部变量时,ThreadLocal
更像是一个辅助类,这个类本身不含有ThreadLocalMap threadLocals
变量,而是通过set或get方法进而操作调用它的线程的threadLocals
变量
参考博客:
https://www.cnblogs.com/fsmly/p/11020641.html
https://blog.csdn.net/qq_33404395/article/details/82356344