我终于把ThreadLocal看懂了

ThreadLocal

参考文档

一、ThreadLocal结构和常用方法

  • set()方法用于保存当前线程的副本变量值。
  • get()方法用于获取当前线程的副本变量值。
  • initialValue()为当前线程初始副本变量值。
  • remove()方法移除当前线程的副本变量值。

v2-a39ad53e2dff823223d5e25dab26ce96_r

二、为什么ThreadLocalMap 设计为ThreadLocal 内部类

主要是说明ThreadLocalMap 是一个线程本地的值,它所有的方法都是private 的,也就意味着除了ThreadLocal 这个类,其他类是不能操作ThreadLocalMap 中的任何方法的,这样就可以对其他类是透明的。同时这个类的权限是包级别的,也就意味着只有同一个包下面的类才能引用ThreadLocalMap 这个类,这也是Thread 为什么可以引用ThreadLocalMap 的原因,因为他们在同一个包下面。

虽然Thread 可以引用ThreadLocalMap,但是不能调用任何ThreadLocalMap 中的方法。这也就是我们平时都是通过ThreadLocal 来获取值和设置值。其实ThreadLdocalMap 对使用者来说是透明的,可以当作空气,我们一值使用的都是ThreadLocal,这样的设计在使用的时候就显得简单,然后封装性又特别好。

三、ThreadLocalMap是线程私有的保证了线程安全

  1. 每个线程都有一个ThreadLoclMap成员变量(线程副本);

  2. ThreadLocal作为ThreadLocalMap的’key’来获取最终变量的值;

        private static ThreadLocal<String> sThreadLocal = new ThreadLocal<>();
        public static void main(String args[]) {
            sThreadLocal.set("这是在主线程中");
            System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
            //线程a
            new Thread(new Runnable() {
                @Override
                public void run() {
                    sThreadLocal.set("这是在线程a中");
                    System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
                }
            }, "线程a").start();
            //线程b
            new Thread(new Runnable() {
                @Override
                public void run() {
                    sThreadLocal.set("这是在线程b中");
                    System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
                }
            }, "线程b").start();
            //线程c
            new Thread(() -> {
                sThreadLocal.set("这是在线程c中");
                System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
            }, "线程c").start();
        }
  1. 虽然多个线程共享一个ThreadLocal,但是ThreadLocalMap是线程私有的,根据当前线程对象获取的,所以并没有任何影响

线程1 对应 map1 -> local -> 对象1

线程2 对应 map2 -> local -> 对象2

线程3 对应 map3 -> local -> 对象3

虽然根据相同的local去获取,但不同线程获取的值不同

  1. 对于一个线程来说一个ThreadLocal对象作为键只能保存一个值,所以一个线程中也可以新建多个ThreadLocal对象

QQ浏览器截图20200830144432

获取Map的方法

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

当我们初始化一个线程的时候其内部干去创建了一个ThreadLocalMap的Map容器待用。

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

虽然是静态内部类但底层是调用构造方法返回实例

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

//构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

四、ThreadLocal中使用弱引用的原因

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

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

当我们创建一个threadlocal时

//此时local对创建的对象是强引用
ThreadLocal<String> local=new ThreadLocal<>();
//以一个键值对的形式<local,"123">//线程的成员属性存入map
local.set("123");

此时创建的对象ThreadLocal被两个地方引用

  1. local的引用

  2. 键值对的引用

以下是示意图

20200529145509582

此时我们需要明确知道threadlocalMap是线程的成员属性, threadlocalMap的生命周期与线程一致当方法运行完毕后,栈帧被弹出 local对堆中对象的强引用消失假设key对堆中的对象引用是强引用会导致其的生命周期与线程生命周期一致导致内存泄漏。

弱引用只要发生了gc就会被回收,但前提是只有弱引用,没有强引用,当方法运行完毕后,栈帧被弹出 local对堆中对象的强引用消失,threadlocal也没用处了,此时只有弱引用指向他。

static class Entry extends WeakReference<ThreadLocal<?>>

因为只有Entry和key的关系是WeakReference,这里仅能做到回收key不能回收value,如果这个线程运行时间非常长,即使referent GC了,value持续不清空,就有内存溢出的风险
彻底回收最好调用remove。remove相当于把ThreadLocalMap里的这个元素干掉了,并没有把自己干掉。

这样的话,如果不用remove怎么都会内存泄漏,但为什么要用弱引用呢?

我们来看set底层源码,若果k=null,则替换掉之前的键值。

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

这样就算忘记remove方法,下次调用set、get、remove方法时value值也将被清除。软引用比弱引用更多了一层保障。

但是
ThreadLocal instances are typically private static fields in classes.
ThreadLocal 对象通常作为私有静态变量使用 那么其生命周期至少不会随着线
程结束而结束。
所以在每次用完 ThreadLocal 时, 还是必须要及时调用 remove()方法清理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值