目录
为什么要使用ThreadLocal?
简单案例
ThreadLocal源码分析
断点跟踪
为什么要使用ThreadLocal
在多线程下,如果同时修改公共变量可能会存在线程安全问题,JDK虽然提供了同步锁与Lock等方法给公共访问资源加锁,但在高并发的场景下,如果多个线程争抢一把锁会出现大量的锁等待时间,让系统的响应时间变慢。因此JDK又提供了新的思路,用空间换取时间也就是使用ThreadLocal。
简单案例
先进行一个简单案例
@SpringBootTest
public class TestThreadLocal {private ThreadLocal<String> local = new ThreadLocal<>();@Testpublic 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);elsecreateMap(t, value);return value;}
initialValue()默认直接返回null,需要我们自己重写实现,具体代码如下
@SpringBootTest
public class TestThreadLocal {private ThreadLocal<User> local = new ThreadLocal<User>(){@Overrideprotected User initialValue() {User user = new User("张三", 18);return user;}};@Testpublic void test03() throws Exception {new Thread(()->{User user = local.get();System.out.println(user.toString());}).start();}
}
运行结果如下
User(name=张三, age=18)
以上就是我对ThreadLocal的简单理解!