简单理解ThreadLocal源码分析

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的路上修行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值