ThreadLocal简介
变量值的共享可以使用public static的形式,所有线程都使用同一个变量,如果想实现每一个线程都有自己的共享变量该如何实现呢?JDK中的ThreadLocal类正是为了解决这样的问题。
ThreadLocal类并不是用来解决多线程环境下的共享变量问题,而是用来提供线程内部的共享变量,在多线程环境下,可以保证各个线程之间的变量互相隔离、相互独立。在线程中,可以通过get()/set()方法来访问变量。ThreadLocal实例通常来说都是private static类型的,它们希望将状态与线程进行关联。这种变量在线程的生命周期内起作用,可以减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
1.threadlocal 源码解析与实际应用
(1)源码解释
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*/
/**
* 翻译:此类提供线程局部变量。 这些变量与普通变量不同,
* 每个访问一个线程(通过其get或set方法)的线程都
* 有其自己的,独立初始化的变量副本。
* ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)。
* 例如,下面的类生成每个线程本地的唯一标识符。
* 线程的ID是在第一次调用ThreadId.get()时分配的,并且在后续调用中保持不变
**/
(2)实际应用
/**
* 线程变量缓存Local
*/
public class UserThreadLocal extends ThreadLocal<UserToken>{
private static ThreadLocal<UserToken> threadLocal = new ThreadLocal<UserToken>();
//设置userToken
public static void setLocal(UserToken userToken){
threadLocal.set(userToken);
}
//获取threadLocal中userToken的实例
public static UserToken getLocal(){
return threadLocal.get();
}
//清楚usertoken实例
public static void removeLocal(){
threadLocal.remove();
}
public static String getToken(){
UserToken userToken=threadLocal.get();
if (userToken != null){
if (StringUtils.isEmpty(userToken.getToken())) {
return JSON.toJSONString(userToken);
}
return userToken.getToken();
}
return null;
}
public static Long getUid(){
UserToken userToken=threadLocal.get();
if (userToken != null){
return userToken.getUid();
}
return null;
}
public static Long getCid(){
UserToken userToken=threadLocal.get();
if (userToken != null){
return userToken.getCid();
}
return null;
}
public static Long getStaffId(){
UserToken userToken=threadLocal.get();
if (userToken != null){
return userToken.getStaffId();
}
return null;
}
public static String getUname(){
UserToken userToken = threadLocal.get();
if (userToken != null){
return userToken.getUname();
}
return null;
}
public static String getCname(){
UserToken userToken = threadLocal.get();
if (userToken != null){
return userToken.getCname();
}
return null;
}
public static Long getSysId(){
UserToken userToken = threadLocal.get();
if (userToken != null){
return userToken.getSysId();
}
return null;
}
public static String getMqId(){
UserToken userToken = threadLocal.get();
if (userToken != null){
return userToken.getMqId();
}
return null;
}
public static String getTxId(){
UserToken userToken = threadLocal.get();
if (userToken != null){
return userToken.getTxId();
}
return null;
}
public static String getLocalTxId(){
UserToken userToken = threadLocal.get();
if (userToken != null){
return userToken.getLocalTxId();
}
return null;
}
//分布式事务id
public static void setLocalTxId(String localTxId) {
UserToken userToken = threadLocal.get();
if (userToken == null) {
userToken = new UserToken();
}
userToken.setLocalTxId(localTxId);
}
//分布式事务父类id
public static String getParentTxId(){
UserToken userToken = threadLocal.get();
if (userToken != null){
return userToken.getParentTxId();
}
return null;
}
(3)解析
上面的set方法源码:设置与当前线程关联的ThreadLocal值。
ThreadLocal最简单的实现方式就是ThreadLocal类内部有一个线程安全的Map,然后用线程的ID作为Map的key,实例对象作为Map的value,这样就能达到各个线程的值隔离的效果。
JDK最早期的ThreadLocal就是这样设计的,但是,之后ThreadLocal的设计换了一种方式,我们先看set()方法的源码,然后进一步介绍ThreadLocal的实现方式
/**
* 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.
*/
/*
*翻译:
*将此线程局部变量的当前线程副本设置为指定值。
*大多数子类将不需要重写此方法,而仅依靠initialValue方法来设置线程initialValue的值。
*参数:
*value –要存储在此本地线程的当前线程副本中的值
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
get()方法主要做了以下事情:
1、调用Thread.currentThread()获取当前线程对象t;
2、根据当前线程对象,调用getMap(Thread)获取线程对应的ThreadLocalMap对象:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
threadLocals是Thread类的成员变量,初始化为null:
/* ThreadLocal values pertaining to this thread. This map is maintained
- by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
3、如果获取的map不为空,则在map中以ThreadLocal的引用作为key来在map中获取对应的value e;
4、若e不为null,则返回e中存储的value值,否则转到步骤5;
5、调用setInitialValue()方法,对线程的ThreadLocalMap对象进行初始化操作,ThreadLocalMap对象的key为ThreadLocal对象,value为initialValue()方法的返回值。
从上面的分析中,可以看到,ThreadLocal的实现离不开ThreadLocalMap类,ThreadLocalMap类是ThreadLocal的静态内部类。每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object。这样的设计主要有以下几点优势:
这样设计之后每个Map的Entry数量变小了:之前是Thread的数量,现在是ThreadLocal的数量,能提高性能;
当Thread销毁之后对应的ThreadLocalMap也就随之销毁了,能减少内存使用量。
get方法源码:获取与当前线程关联的ThreadLocal值
/**
* 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();