3.Java线程基础--ThreadLocal线程局部变量

ThreadLocal线程局部变量

ThreadLocal

共享数据是并发数据最核心的问题之一,对于继承了Thread或者实现Runnable接口的对象来说尤其重要

如果对象是实现了Runnable接口,那传入的参数将被多个线程共享,任意线程修改都还会影响到其他线程,会产生一些安全隐患

在某些情况某些属性不需要线程之间共享,java并发Api则为我们提供了线程局部变量ThreadLocal,极大的提供了方便

基本方法

set方法

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);
    }
  1. set方法会判断map是否存在,否则创建一个map对象,类型是ThreadLocalMap
  2. createMap方法会创建一个新的ThreadLocalMap对象,同时当前线程的threadLocals变量引用这个map对象

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

  1. getMap方法获取的就是 t.threadLocals变量的内容
  2. map.set方法是核心逻辑,底层使用Entry数组,根据hash值计算数组下标,然后根据下标获取是否
private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

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

值得一提的是,由于弱引用导致的key为null的情况,会调用replaceStaleEntry方法,此方法会设置key
是null,同时把value值为null,供GC垃圾回收

        if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }

上面提到的ThreadLocalMap使用Entry数组存放元素,可以看到Entry是实现了WeakReference,其中ThreadLocal对象是弱引用key值
在这里插入图片描述
这里也是出现内存泄露的主要原因,当业务代码用完ThreadLocal对象,ThreadLocal被回收时,由于没有强引用关联ThreadLocal,只有一个ThreadLocalMap关联弱引用的ThreadLocal,那么GC回收时该对象将会被回收,但是由于线程没有被销毁,始终强引用ThreadLocalMap对象,则value内容不会被回收,此时value没有任何用处但是也无法回收

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

根据当前ThreadLocal对象获取ThreadLocalMap.Entry, 取到值返回。
如果没有获取到,则进行初始化方法,基本和set方法一致

remove方法

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

ThreadLocalMap的remove方法:

 private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

根据hashcode定位到元素位置,之后进行clear方法

内存泄漏

刚刚上面也提到了,由于Entry的key使用的弱引用,当业务代码使用完之后,没有强引用在引用ThreadLocal对象,那么GC将要回收这个只有ThreadLocalMap引用的弱引用ThreadLocal对象,导致ThreadLocalMap的key为null,但是value是强引用无法被回收,导致内存泄漏

详细可以参考此文章:
https://blog.csdn.net/JH39456194/article/details/107304997

本文如有任何问题,请留言指教,以免误导他人

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值