目录
为什么要使用ThreadLocal?
简单案例
ThreadLocal源码分析
断点跟踪
为什么要使用ThreadLocal
在多线程下,如果同时修改公共变量可能会存在线程安全问题,JDK虽然提供了同步锁与Lock等方法给公共访问资源加锁,但在高并发的场景下,如果多个线程争抢一把锁会出现大量的锁等待时间,让系统的响应时间变慢。因此JDK又提供了新的思路,用空间换取时间也就是使用ThreadLocal。
简单案例
先进行一个简单案例
@SpringBootTest
public class TestThreadLocal {
private ThreadLocal<String> local = new ThreadLocal<>();
@Test
public void test01() throws Exception {
new Thread(()->{
local.set("local_A");
System.out.println(Thread.currentThread().getName());
System.out.println(local.get());
},"a").start();
new Thread(()->{
local.set("local_B");
System.out.println(Thread.currentThread().getName());
System.out.println(local.get());
},"b").start();
}
}
运行结果为如下
a
local_A
b
local_B
待会我们断点查看上述代码的运行过程,在此之前我们先来查看一下ThreadLocal源码。
ThreadLocal源码分析
首先给出ThreadLocal类中主要的代码如下
public class ThreadLocal<T> {
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的成员变量ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
//根据threadLocal对象从map中获取Entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//获取保存的数据
T result = (T)e.value;
return result;
}
}
//如果没有进行set方法,那么执行该方法
//初始化数据
return setInitialValue();
}
private T setInitialValue() {
//获取要初始化的数据,该方法需要自己重写实现
T value = initialValue();
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的成员变量ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果map不为空
if (map != null)
//将初始值设置到map中,key是this,即threadLocal对象,value是初始值
map.set(this, value);
else
//如果map为空,则需要创建新的map对象
createMap(t, value);
return value;
}
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的成员变量ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果map不为空
if (map != null)
//将值设置到map中,key是this,即threadLocal对象,value是传入的value值
map.set(this, value);
else
//如果map为空,则需要创建新的map对象
createMap(t, value);
}
static class ThreadLocalMap {
...
}
...
}
上面三个主要方法主要操作的是ThreadLocalMap对象,而ThreadLocalMap又是ThreadLocal的静态内部类。
主要的ThreadLocalMap的源码如下
static class ThreadLocalMap {
//维护了一个静态内部类,用来存储主要的数据
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//存在一个Entry数组,该table对象中主要存放多个ThreadLocal与value的值
private Entry[] table;
...
}
ThreadLocalMap中又维护了一个内部类Entry,同时Entry类又继承了WeakReference(弱引用与ThreadLocal对象)。
在Thread类中维护了一个ThreadLocalMap类型的变量threadLocals。
因此ThreadLocal的整体结构大致如下
每一个set()的数据都会被保存在Entry数组中。源码查看完毕,接下来我们断点跟踪上述案例。
断点跟踪
对set()方法中进行断点查看,当线程a第一次执行set()时,先获取ThreadLocalMap对象是否为空,如果为空,那么执行createMap()方法,将当前线程与需要存储的值作为参数传递
在createMap()中,只做了一件事,那就是创建一个对象并放入当前线程。
set()方法查看完毕,接下来查看get()方法做了什么事情
在get方法中,获取当前线程以及线程中的map对象,从对象中根据key获取存储的值并返回。如果没有执行过set方法,会执行setInitalValue()方法。
而在该方法中其实也只做了一件事,那就是初始化值。
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
initialValue()默认直接返回null,需要我们自己重写实现,具体代码如下
@SpringBootTest
public class TestThreadLocal {
private ThreadLocal<User> local = new ThreadLocal<User>(){
@Override
protected User initialValue() {
User user = new User("张三", 18);
return user;
}
};
@Test
public void test03() throws Exception {
new Thread(()->{
User user = local.get();
System.out.println(user.toString());
}).start();
}
}
运行结果如下
User(name=张三, age=18)
以上就是我对ThreadLocal的简单理解!