什么是ThreadLocal

什么是ThreadLocal

1、简单使用

简单来说,ThreadLocal就是线程局部变量,是为了实现线程间的数据隔离.
下面来看一个小例子,在main线程中我们给t1设置了值,延时一秒使main线程在thread1线程前执行

public class ThreadLocalTest {
    //新建一个ThreadLocal对象
    static ThreadLocal<String> t1 = new ThreadLocal<>();
    public static void main(String[] args) throws InterruptedException {
        t1.set("threadLocal");//调用set方法设置值
        System.out.println(Thread.currentThread().getName() + ":" + t1.get());
        Thread.sleep(1000);
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + ":" + t1.get());
        },"thread1").start();
        t1.remove();
    }
}
//执行结果
main:threadLocal
thread1:null

从上面的例子我们可以看出,t1在main线程中设置了值,但是只能在main线程中get到,在thread1中get不到值,实现了线程间数据的隔离

为什么会这样,ThreadLocal是如何保存数据的?

2、源码初探

//public class ThreadLocal<T>
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);//getMap
    if (map != null)
        map.set(this, value);//这里的this指的是ThreadLocal t1
    else
        createMap(t, value);
}
//getMap方法拿到当前线程的成员变量threadLocals
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
//public class Thread implements Runnable
ThreadLocal.ThreadLocalMap threadLocals = null;

通过ThreadLocal的set方法源码,我们可以看出,t1.set("threadLocal")并不是往t1中设置了值,而是往当前线程的成员变量ThreadLocalMap中put值,看完上面的源码后我们大胆猜测set后的结果如下图所示:
在这里插入图片描述
然而事实并非我们所想,源码中ThreadLocalMap的Key值采用的是key弱引用对象。

让我们看看set方法源码

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;	//熟悉的entry数组?
    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) {		//找到K值,覆盖之前的值
            e.value = value;
            return;
        }

        if (k == null) {//先忽略~~
            replaceStaleEntry(key, value, i);
            return;
        }
    }
	//没有找到K值,则new一个新的Entry对象,看看Entry的构造方法
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
	//使用弱引用当做Entry的K值
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

这里我们可以看到,源码中使用ThreadLocal对象的弱引用。

个人理解:假设main线程中的t1不再使用,也就是说t1指向堆内存中ThreadLocal对象的引用没有后,main线程ThreadLocalMap的其中一个key会一直有引用指向此ThreadLocal对象,导致GC无法收集,造成 内存泄露。使用弱引用可以有效的防止内存泄漏问题,弱引用是只要发生GC就会被回收

在这里插入图片描述

你以为到这里就结束了吗?并没有,当ThreadLocal对象被GC回收以后,我们的ThreadLocalMap会存在一个Key值为null的Entry对象,这个对象的value值是无法拿到的,这就产生了新的问题。。。

​ 要解决这个问题,就要求我们不再使用ThreadLocal时,调用ThreadLocal.remove()方法清楚Map中的数据。这时候心中又产生了一丝丝疑惑:既然都有了remove方法,那还需要弱引用干啥?

remove方法源码

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

private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    //算出下标,循环找到对应的Entry对象clear掉
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

看了remove的源码后,感觉弱引用就是为了防止我没有调用remove方法,一定程度的降低内存泄漏问题的存在,使用ThreadLocal千万记得remove。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值