ThreadLocal.java介绍
序言
这个类提供线程局部变量。这些变量与普通变量的不同之处在于,每个访问这些变量的线程(通过其get或set方法)都有自己独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,它们希望将状态与线程(例如,用户ID或事务ID)关联起来。
上面这段话是JDK源码中ThreadLocal类的javadoc的翻译版本,下面我们来解释一下该定义:
1: 什么是线程局部变量?
线程局部变量就是只有线程实例自己可以访问的变量,对其他线程是互斥的(无论是同类线程还是非同类线程)。
2:为什么说ThreadLocal实例通常是类中的私有静态字段?
一般而言,ThreadLocal会被声明为private static final, 因为ThreadLocal有一个特殊作用-----作为map的key值。但是这个不是绝对的,根据需求来变化。
一:ThreadLocal实现原理
这里要说一句“违背”常理的结论,我们使用功能ThreadLocal 的时候都是直接使用get和set方法,很多人会人为变量是保存在ThreadLocal中的,但是实际上ThreadLocal不保存任何变量和数据,数据都保存在了Thread中,所以ThreadLocal这个名字就很应景----线程本地变量。
每个Thread中都有一个ThreadLocalMap成员变量,叫做threadLocals,该变量用于保存和线程关联的数据,但是我们又不能直接访问这个变量,因为他不是public的,那我们应该怎么访问他呢?答案是通过ThreadLocal访问,这也就是ThreadLocal的核心作用----控制threadLocals的访问和初始化。
看一段ThreadLocal的常规用法代码
public class RequestContext {
/**
* ThreadLocal是单例的,因为ThreadLocalMap是用ThreadLocal作为key的,
* 如果不是单例的,同一个线程就不可以创建多个包含ThreadLocal的类的实例,
* 获取访问这样的实例的时候得到我们不期望的结果
*/
private final static ThreadLocal<Map<String, PuppyRequest>> requestsLocalVar =
ThreadLocal.withInitial(() -> {
Map<String, PuppyRequest> data = new HashMap<>();
System.out.println("线程" + Thread.currentThread().getName() +
"初始化map=" + System.identityHashCode(data));
return data;
});
private RequestContext() {
}
public static PuppyRequest getRequest() {
Map<String, PuppyRequest> requestMap = requestsLocalVar.get();
if (requestMap == null) {
requestMap = new HashMap<>();
requestsLocalVar.set(requestMap);
}
System.out.println("requestMap=" + System.identityHashCode(requestMap));
PuppyRequest request = requestMap.get(Thread.currentThread().getName());
if (request == null) {
request = new PuppyRequest();
requestMap.put(Thread.currentThread().getName(), request);
}
return request;
}
}
这段代码中有两个核心部分,第一:声明requestsLocalVar 变量,第二:声明getRequest()方法。
第一部分创建了一个ThreadLocal变量,注意:没有使用功能new,而是用ThreadLocal.withInitial,传递了一个lambda表达式,该表达式表示的是ThreadLocal的initialValue()方法要执行的代码。
第二部分getRequest()方法,用于获取一个PuppyRequest对象,这个方法是支持并发访问的,并且没有使用锁,他先调用ThreadLocal的get方法,获取一个Map,如果map为空则创建一个map,在调用set方法,这里我们有疑问,这里没有加锁,那么多线程访问requestsLocalVar会不会发生数据错乱呢?
答案是显然的,肯定不会,为什么,请看下面个ThreadLocal get方法讲解
ThreadLocal#get()
看代码实现
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
get方法的作用是访问该ThreadLocal关联的对象,如何访问的从代码中就可以看出来
1.获取当前线程
2.根据当前线程获取一个ThreadLocalMap
3.如果ThreadLocalMap不等于null,则根据该ThreadLocal读取map中的一个Entry,
4.返回entry中保存的value,这个value就是requestsLocalVar 变量声明的要本地化的Map<String, PuppyRequest>
5.如果ThreadLocalMap等于null,则进行执行初始化的方法。初始化方法做了什么?
看setInitialValue方法的代码
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
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;
}
1.调用initialValue()方法得到一个类型为T的返回值
2.根据当前线程获取ThreadLocalMap
3.如果map不等于null,则将该ThreadLocal和前面提供的初始值关联起来,ThreadLocal作为key,前面的返回值作为value
4.如果map等于空,则调用createMap(t,value)方法。
不难看出createMap方法回去创建ThreadLocalMap,这样才能存在前面初始化方法提供的value,
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
从createMap方法的代码可以看出,只是new了一个ThreadLocalMap,并赋值给线程对象的threadLocals对象。
看到这里我们就应该明白了一个结论:
ThreadLocal会为不同的Thread创建一个ThreadLocalMap来保存和当前线程相关的对象(状态),
不同线程访问ThreadLocal的时候,ThreadLocal就会取到为该thread创建的map,然后从该map中取到
对象,然后返回,这样就实现了线程本地变量,该变量保存的数据就只有该线程可以访问,用一种更为直观的方式理解set方法,请看如下代码
public T get(Thread t) {}
将ThreadLocal的get方法加上参数Thread,这样看起来就相当于取t的thread local(线程本地)变量
ThreadLocal#set()
看代码实现
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
1.取当前线程
2.根据当前线程获取ThreadLocalMap
3.如果map不等于null,则把ThreadLocal和value关联
4.如果map等于null,则创建map,并把value保存的map
看了上面的get方法,其实set方法也就更好理解了,同理看如下代码
public T set(Thread t,T value) {}
为线程t,保存一个本地变量value,下次就可以通过get方法,取回value。
二:总结
结论就是:
每个线程都各自保存着自己相关的变量和值,ThreadLocal只是给我们开放了访问Thread变量的接口。Thread存储变量用的是ThreadLocalMap,该map是以ThreadLocal为key,Thread要保存的值为value的一个map实现