ThreadLocal及其应用浅析

ThreadLocal及其应用浅析

参考视频:https://www.bilibili.com/video/BV1fA411b7SX?from=search&seid=4082488691777652024
参考博客:https://www.cnblogs.com/micrari/p/6790229.html#3835852

TheadLocal<T> tl = new ThreadLocal<>();
dosomething();
tl.set(x);

ThreadLocal将变量副本与线程进行绑定,实现了变量的线程隔离。这也意味着,tl.set(x)完成后,即便没有显示地传参,也可以在该线程执行的过程中,在想要的位置取值tl.get()。在spring的@Transactional,mybatis的分页插件PageHelper,链日志中都有应用。

ThreadLocal原理

ThreadLcoal的源码注释较为详细,源码量也十分友好,能比较方便地阅读,这里将对一些关键代码进行梳理。当ThreadLocal对象进行set操作设置本地变量时会从当前线程中取到一个ThreadLocalMap

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);  //click
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;  //每个线程都有一个类型为ThreadLocal.ThreadLocalMap的字段threadLocals
    }

ThreadLocalMap是ThreadLocal中的一个内部类,它里面还有一个Entry的内部类,Entry封装了一个键值对,以ThreadLocal为键,以需要装载的本地变量为value。
在这里插入图片描述
因为一个线程里可以有多个ThreadLocal,所以ThreadLocalMap设计了Entry数组private Entry[] table;

ThreadLocal的内存泄露问题

原理图

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

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

从源码不难看出Entry(ThreadLocal<?> k, Object v)中的key是对ThreadLocal的一个弱引用。结合原理图,①当tl=null时,内存中内被new出来的ThreadLocal由于只被弱引用,该块内存将被回收。加入该弱引用改为强引用,即便tl=null,图中的ThreadLocal也不会被回收,弱引用解决了这一块的内存泄露问题。②但是ThreadLocalMap中保存的本地变量10Mvalue仍然被强引用,如果线程长时间不进行get和set(当ThreadLocal进行get和set的时候将会清理这些key=null的值),那么这块内存将不会被回收,这就造成了内存泄露的问题。下面对此进行验证:
在这里插入图片描述
可以发现,utl=null然后进行垃圾回收,Entry中的value依旧存在。所以在使用threadLocal时要养成显示remove的习惯,它还能解决线程复用带来的问题。

@Slf4j
@RestController
public class ThreadLocalTestController {
    private static ThreadLocal<Integer> i = new ThreadLocal<>();
    @GetMapping("testsingleton1")
    @ResponseBody
    public int test1() {
        if (i.get() == null) {
            i.set(0);
        }
        i.set(i.get().intValue() + 1);
        log.info("{} -> {}", Thread.currentThread().getName(), i.get());
        int res = i.get().intValue();
//        i.remove();  //避免线程复用带来并发问题
        return res;
    }
}

controller中有一个ThreadLocal,每当有请求就会对ThreadLocal中的i值加1。测试时发了30个并发请求,对于这30个请求,期望打印的日志中i值都为1,但是实际上i值被累加
在这里插入图片描述
原因在于spring开启了线程池,线程被复用了,i.get().intValue()拿到的是线程直线被使用的值。为了解决这个问题需要手动对ThreadLocal进行remove,即把上面代码中的注释打开。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值