ThreadLocal实现原理与实战运用

原始链接ThreadLocal实现原理与实战运用

一、ThreadLocal简介

ThreadLocal(线程本地)叫做线程变量,属于当前线程,该变量对其他线程而言是隔离的,也就是该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

  1. 同一个ThreadLocal所包含的对象,在不同的Thread中有不同的副本。
  2. 因为每个线程Thread内有自己的副本,该副本只能由当前Thread使用。
  3. 既然每个Thread有自己的实列副本,且其他Thread不可访问,那就不存在多线程间共享的问题。

二、开发中实际使用

1、拦截器中使用根据token 查询出用户信息后存储线程副本ThreadLocal。

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler )
            throws Exception {
        log.info("===================进入拦截器===================");
        String token = request.getHeader("Authorization");
        .....
        LoginUser loginUser = (LoginUser)redisService.get(token.replace(CacheConstants.TOKEN_PREFIX,""));
        if (loginUser != null) {
            UserInfoUtil.setLoginUser(loginUser);
            tokenService.refreshToken(loginUser);
        }
        return true;
    }
2、怎么存储。
public class UserInfoUtil {

    // 用户信息
    private static ThreadLocal<LoginUser> loginUserThreadLocal = new ThreadLocal<>();

    /**
     * 设置当前线程登录用户信息
     */
    public static void setLoginUser(LoginUser loginUser) {
        loginUserThreadLocal.set(loginUser);
    }

    /**
     * 设置当前线程登录用户信息
     */
    public static LoginUser getLoginUser() {
        LoginUser loginUser = loginUserThreadLocal.get();
        return loginUser;
    }
3、怎么使用
public Order saveOrder(Order order) throws Exception {
        LoginUser loginUser =  UserInfoUtil.getLoginUser(){
        if(loginUser == null){
            order.setCreateBy(loginUser.getName)
        }
        return order;
    }

三、源码分析

public class ThreadLocalThread {
    public static void main(String[] args) {
        ThreadLocal<String> threadLocalName = new ThreadLocal<>();
        ThreadLocal<String> threadLocalToken = new ThreadLocal<>();
        threadLocalName.set("微信公众号:攻城狮小章鱼");
        threadLocalToken.set("8fjkajfei3reajfoea3urejfejoier3ur");
        System.out.println(threadLocalName.get());
    }
}

1、set()方法、通过Thread.currentThread()获取当前线程t。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

2、调用getMap(t)获取当前线程的( ThreadLocalMap) map。

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }	

3、t.threadLocals 是Thread的一个属性(ThreadLocal.ThreadLocalMap threadLocals = null;)返回的是一个ThreadLocalMap的值。
4、执行if (map != null) 判断是否为null 不为null直接调用set()方法存值,如果为null,调用createMap创建一个new ThreadLocalMap(this, firstValue); this是当前的(ThreadLocal 对象)threadLocalName,firstValue是(“微信公众号:攻城狮小章鱼”)

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
 }

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];//ThreadLocal定义的一个初始容量 16
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//进行hash计算
    table[i] = new Entry(firstKey, firstValue);
    size = 1;//第一次创建的时候长度是1  
    setThreshold(INITIAL_CAPACITY);
}

5、Entry继承了WeakReference ,super(k)是一个弱引用,所以说Entry的key是ThreadLocal(是一个弱引用)

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

6、通过以上几步一个第一次value就成功的存到Thread的本地副本里了l
数据存储示列:

7、当一个线程有多个ThreadLocal怎么样存储。当再次调用set()方法时ThreadLocalMap不为空的时候调用map.set_(this, value)_。

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();
}

8、多个ThreadLocal数据存储示列。

四、内存泄漏

1、使用ThreadLocal时,一般建议将其声明为static final的,避免频繁创建ThreadLocal实例。
2、尽量避免存储大对象,如果非要存,那么尽量在访问完成后及时调用remove()删除掉。

五、总结

ThreadLocal不是洪水猛兽,不要听到「内存泄漏」就不敢使用它,只要你规范化使用是不会有问题的。再者,就算你不规范使用,ThreadLocal也做出了很多努力来最大程度的帮你避免发生「内存泄漏」。
前面已经说过,由于Key是弱引用,因此ThreadLocal可以通过key.get()==null来判断Key是否已经被回收,如果Key被回收,就说明当前Entry是一个废弃的过期节点,ThreadLocal会自发的将其清理掉。
ThreadLocal会在以下过程中清理过期节点:
1、调用set()方法时,采样清理、全量清理,扩容时还会继续检查。
2、调用get()方法,没有直接命中,向后环形查找时(expungeStaleEntry())。
3、调用remove()时,除了清理当前Entry,还会向后继续清理。

版权归属
ca93f9bd5dd842f9f5e349408b4871b3.jpg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值