ThreadLocal是线程局部变量,所谓的线程局部变量,就是仅仅只能被本线程访问,不能在线程之间进行共享访问的变量。
项目为了在各个时候获取用户信息,每次都会把请求的ticket值使用工具类里的ThreadLocal存起来,然后通过一个工具服务bean去获取ThreadLocal各自线程的存入的ticket,这样就可以到处注解注入工具服务bean使用了用户的信息了。
public class TicketUtil {
private static ThreadLocal<UserInfo> LOCAL_USER_CONTEXT_TL = new ThreadLocal();
public static String getTicket() {
UserInfo userInfo = LOCAL_USER_CONTEXT_TL.get();
if (userInfo == null) {
return null;
}
return userInfo.getTicket();
}
public static void setTicket(String ticket) {
UserInfo userInfo = LOCAL_USER_CONTEXT_TL.get();
if (userInfo == null) {
userInfo = new UserInfo();
LOCAL_USER_CONTEXT_TL.set(userInfo);
}
userInfo.setTicket(ticket);
}
public static UserInfo getUserContext() {
UserInfo userInfo = LOCAL_USER_CONTEXT_TL.get();
return userInfo;
}
public static void cleanUserContext() {
UserInfo userInfo = LOCAL_USER_CONTEXT_TL.get();
LOCAL_USER_CONTEXT_TL.remove();
}
}
@Getter
@Setter
class UserInfo {
private String ticket;
}
服务类
@Service
public class SessionService {
//简写了
/**
* 根据ticket获得用户对象
*
* @return
*/
public UserCacheDto getSessionUser() {
String ticket = TicketUtil.getTicket();
if (StringUtils.isEmpty(ticket)) {
return null;
}
String userCacheString = redisUtil.hget(SESSION_PREFIX + ticket, USER_CACHE_FIELD);
UserCacheDto userCacheDto = JsonUtils.fromJson(userCacheString, UserCacheDto.class);
return userCacheDto;
}
}
现在简单分析一下ThreadLocal源码
第一步、分析这个值
private final int threadLocalHashCode = nextHashCode();
就是数值,可以理解给当前的ThreadLocal对象的一个唯一标识吧。
第二步、set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
就是看获取当前线程里面的ThreadLocalMap。
这是一个map,用于把我们创建的ThreadLocal对象LOCAL_USER_CONTEXT_TL 放入到当前线程Thread里的变量ThreadLocalMap里。为什么呢?因为我们可以创建很多的ThreadLocal对象,这些都应该放在线程里面,那怎么单独的放呢,使用数组,
那这个map有什么呢
static class ThreadLocalMap {
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
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();
}
}
我们看到,线程的map里有一个Entry类,里面就2个值,一个是ThreadLocal类的对象LOCAL_USER_CONTEXT_TL的引用,一个就是我们自己的UserInfo对象。所以就是存在了这里。
若有多个ThreadLocal类的对象呢,那就要使用Entry[] tab数组了。
怎么知道使用本次的ThreadLocal类的对象LOCAL_USER_CONTEXT_TL放在哪里呢,使用与运算key.threadLocalHashCode & (len-1)
,其中threadLocalHashCode 就是当前LOCAL_USER_CONTEXT_TL的唯一标识。
第三步,get
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();
}
这就很清晰了,获取当前线程Thread t,然后获取该线程的ThreadLocalMap对象,然后看当先LOCAL_USER_CONTEXT_TL的是否存在Entry数组的一个元素Entry中(其实里面获取逻辑更加复杂,有兴趣可以自行看看),若获取到了,那就这个元素Entry的value值就好。
第四步,remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
获取当前线程的map,然后删除数组Entry里的那个实际的元素Entry,删除实际的元素Entry的时候也要把Entry里对ThreadLocal对象LOCAL_USER_CONTEXT_TL的引用clear掉,即this.referent = null;
。另外Entry extends WeakReference<ThreadLocal<?>>
使用了弱引用,只要gc扫描到了废弃是对象LOCAL_USER_CONTEXT_TL,就放入一个ReferenceQueue等待清理。
【总结】
实际把ThreadLocal对象被各自线程的ThreadLocalMap的Entry数组的某个Entry元素的reference引用,且Entry元素value存储的就是我们想设置的值。
以上即是ThreadLocal的实际应用和简单理解。
正在去往BAT的路上修行