1 简介
ThreadLocal提供了线程内存储变量的能力。
2 ThreadLocal的应用场景
如下图,方法1调用方法2,方法2调用方法3,方法3调用方法4
如果我们想要在方法4中使用方法1中的一个变量sa,可以怎么做?
1)通过参数传递
在某些情况下可以,但是如果中间某个方法(如method2)是其它人提供的api,我们不能去更改这个方法的时候,就不能通过参数传递
2)把sa提出来定义为全局变量
这样子虽然可以访问,但是会有线程安全问题
3)使用ThreadLocal
3 ThreadLocal的实际应用
在使用spring的过程中,我们进行事务管理,通常会使用@Transactional注解。它就是应用了ThreadLocal
同样的,如上图,方法一层层调用,要保证几个方法事务,那么它们几个方法必须使用同一个连接。不能方法1使用连接1,方法2使用连接2,这样子没有办法保证事务。那么各个方法怎么样获取到同一个连接呢,spring就使用了ThreadLocal
4 示例
4.1 代码
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocal<PojoTest> tl = new ThreadLocal<>();
new Thread(()->{
tl.set(new PojoTest());
System.out.println(tl.get());
}).start();
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tl.get());
}).start();
}
}
4.2 执行结果
com.ruoyi.weixin.user.AopTest.PojoTest@75e3668f
null
4.3 执行说明
1)创建了一个ThreadLocal
2)在第一个线程中给这个ThreadLocal对象set一个PojoTest对象
3)在第一个线程中get去获取PojoTest对象,获取到了
4)但是在第二个线程中去获取的时候没有获取到
说明ThreadLocal里面存储的对象是线程隔离的
5 源码(set方法)
5.1 set方法
public void set(T value) { //1 获取当前线程(调用者线程) Thread t = Thread.currentThread(); //2 获取到当前线程的一个属性threadLocals,它是一个map ThreadLocalMap map = getMap(t); if (map != null)
//3 如果map不为null,直接把值存入map中,key就是当前的ThreadLocal实例对象
map.set(this, value);
else
// 4 如果map为空,创建map,再存值
createMap(t, value); }
5.1.1 getMap()方法
getMap方法很简单,直接返回了线程对象的一个变量threadLocals
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
5.1.2 再看一下threadLocals这个变量
发现它的类型是一个ThreadLocal的内部类ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals = null;
也就是说getMap方法是获取到Thread的一个类型为ThreadLocalMap的变量
5.1.3 ThreadLocalMap的set方法
map.set(this, value);
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
这个方法中,其它代码不用看,看最关键的一行代码,创建了一个Entry对象,它就是一个键值对对象
tab[i] = new Entry(key, value);
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry继承WeakReference(弱引用),WeakReference继承Reference,Reference中有一个成员变量:private T referent;
也就是说Entry有两个变量:T referent 和 Object value
关于弱引用:https://www.cnblogs.com/jthr/p/15960725.html
我们看Entry的构造方法
1)super(k);
这行代码调用父类WeakReference的构造方法,使用ThreadLocal创建了一个WeakReference对象,并把它赋值给了referent
2)value = v;
把存储对象赋值给value
5.2 存储关系图
ThreadLocal.set(x)
实际上是把数据存储到了当前线程的成员变量ThreadLocalMap threadLocals中
ThreadLocalMap中维护了一个Entry数组
Entry里面存储KEY-VALUE键值对,KEY是ThreadLocal的WeekReference对象,VALUE是要存储的对象
6 为什么要使用弱引用
我们创建了一个ThreadLocal对象,并调用了set方法
那么这个ThreadLocal对象就会有两个引用,一个是t指向它的一个强引用,一个是Entry的key(referent属性)指向它的一个弱引用。
当我们把t设置为null的时候,在垃圾回收的时候,这个ThreadLocal由于只有一个弱引用指向它,就会被回收
如果不使用弱引用,那么Entry的key(referent属性)指向它的就是一个强引用,即便我们把t设为null,它也不会被回收。只有在这个线程清空key(spring中线程使用完回到线程池会清空threadLocals)或者线程本身被销毁的时候,ThreadLocal才能被回收。
7 示例及图解
public static void main(String[] args) {
ThreadLocal<Integer> tc = new ThreadLocal();
Thread t1 = new Thread(()->{
try {
tc.set(123);
System.out.println("线程一" + tc.get());
} catch (Exception e) {
e.printStackTrace();
}
},"t1");
t1.start();
Thread t2 = new Thread(()->{
try {
tc.set(456);
System.out.println("线程二" + tc.get());
} catch (Exception e) {
e.printStackTrace();
}
},"t1");
t2.start();
}
threallocal.set实际上把值存在了当前Thread里面的一个属性里面,这个属性是一个map。key就是threallocal对象,value就是值。key-value对象在数组中的位置通过key的hash值对数组长度取余获取,所以,一个ThreadLocal对象能够为线程提供一个key-value对象,同一个ThreadLocal对象对一个线程set的时候,多次set是会覆盖的。