panda理解的ThreadLocal

之前一直觉得ThreadLocal是一个很高大上的东西,可望而不可即,直到今天老师讲课用到ThreadLocal,一笔带过了,说大家都懂了不细说了,what?? 大家都会了?那panda还心思啥呢,抓紧偷偷学起来呀。。看了几篇文章发现,奥~ 就那么回事,做个笔记吧,万一有小白喜欢看panda这种大白话风格呢。。吼吼

一、ThreadLocal简介

ThreadLocal - 线程本地变量 ,
线程本地啥意思呢,就是线程自己的,别的线程你拿不到,哈哈
然后它本地变量说明它就是个变量,存值用的

在idea中打开ThreadLocal类,点击定位,你就会看到它是JDK包提供的,在rt.jar包中java.lang包下
在这里插入图片描述

二、ThreadLocal简单使用

眼见为实,我们来搞个小demo康康吧
1、首先创建一个ThreadLocal变量
2、开个新线程t1,t1线程里给ThreadLocal变量塞值,打印出来
3、开个新线程t2, 打印ThreadLocal变量里的值

所以我们的意图就是康康线程2能否拿到线程1放的ThreadLocal变量值嘛

package com.lagou.edu.test;
public class ThreadLocalTest {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("hello panda");
                System.out.println("t1线程获取ThreadLocal变量里的值为:"+threadLocal.get());
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t2线程获取ThreadLocal变量里的值为:"+threadLocal.get());
            }
        });
        t1.start();
        t2.start();
    }
}

执行结果证明,拿不到,嘎嘎
在这里插入图片描述

三.深入解析ThreadLocal类

那么ThreadLocal它是怎么做到的呢,我们来康康吧。。

先来看看它都提供了哪些api,在idea中打开ThreadLocal类,鼠标右键->Diagrams(类图)->show Diagram…
在这里插入图片描述
会出现下面类图,想展示啥自己选,我们点击m(methods) 来康康它的api,最关键的就是

get() - 获取ThreadLocal在当前线程中保存的变量副本
set()- 设置当前线程中变量的副本
remove() - 移除当前线程中变量的副本
withInitial() - 初始化
在这里插入图片描述

我们再来搞个小demo覆盖一下这几个api , 这不就用起来了。。
在这里插入图片描述

下面我们来康康它们的源码:

set() - 设置当前线程中本地变量的副本

public void set(T value) {
    //(1)获取当前线程(调用者线程)
    Thread t = Thread.currentThread();
    //(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //(3)如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
    if (map != null)
        map.set(this, value);
    //(4)如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
}

让我们逐句来分析一下:

1、Thread t = Thread.currentThread(); 获取当前线程,没啥说的

2、ThreadLocalMap map = getMap(t);
调用本类的getMap()方法,把当前线程作为参数扔进去了,下面我们来看一下getMap()方法

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

返回的是ThreadLocalMap 类型的对象,
然后return的是t的threadLocals属性,
也就是说Thread类里面有个属性是ThreadLocalMap 类型的 ,如下图
在这里插入图片描述
那我们再来康康ThreadLocalMap这个类是干啥的:
我们会看到,奥,它是ThreadLocal的静态内部类,内部结构和HashMap差不多,搞个Entry数组存放数据
在这里插入图片描述

那我们先小小梳理一下:

ThreadLocal类有个内部类ThreadLocalMap用来存放数据的,线程Thread将ThreadLocalMap引用过来作为自己的属性,感觉到这已经有答案了啊,我们继续吧。。
在这里插入图片描述

   if (map != null)
        map.set(this, value);
    else
        createMap(t, value);

这句就是如果刚才拿到了ThreadLocalMap对象,则给他塞值,
塞值这非常关键,首先key = this,this是谁,当前对象ThreadLocal的引用啊,当前引用它的是谁,当前Thread啊,value = value(就是你放的那个值,“hello panda”),当我们去获取变量值get的时候也是通过key去获取值,每个线程拿的key不一样,获取的value当然不一样啦,当然你的数据别人就拿不到啦,就线程隔离啦

else
 createMap(t, value);

如果当前线程获取的map为空则走else,
因为Thread里面threadLocals 默认值是null

 ThreadLocal.ThreadLocalMap threadLocals = null;
  ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

所以我们首次set值时肯定要先创建map再赋值

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

让我们再进一步,康康ThreadLocalMap构造具体怎么塞值的,其实就是将我们传过来的this和value放到Entry里,在哈希散列一下,决定我们Entry放到table数组的哪个位置

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

所以以上做了那么多,其实就是将ThreadLocalMap塞好值引用赋给Thread类的属性threadLocals,
所以副本是什么,引用啊,真正变量的值在ThreadLocal里呢

get() - 获取ThreadLocal在当前线程中保存的变量副本

public T get() {
    //(1)获取当前线程
    Thread t = Thread.currentThread();
    //(2)获取当前线程的threadLocals变量
    ThreadLocalMap map = getMap(t);
    //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
    return setInitialValue();
}

get就很好理解了,
1、获取当前线程
2、获取当前线程的ThreadLocalMap 属性,此时应该已经塞好值了
3、将当前线程引用作为key去获取我们的变量值value ,返回
4、如果map为空先去初始化

remove() - 移除当前线程中变量的副本

public void remove() {
    //获取当前线程绑定的threadLocals
     ThreadLocalMap m = getMap(Thread.currentThread());
     //如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
     if (m != null)
         m.remove(this);
 }

remove也很好理解,变量不用了,要从map中干掉
1、获取当前线程
2、获取当前线程的ThreadLocalMap 属性
3、将当前线程引用作为key删除map中元素

综上,我们整理一下,每个线程Thread维护一份自己的ThreadLocalMap属性threadLocals,而线程变量ThreadLocal是交给内部类ThreadLocalMap去做变量值的存储,map存的值为当前线程对ThreadLocal的引用this及我们塞的变量值,就如下图,不同线程的变量值虽然都在ThreadLocal存储,但是每个线程都自己维护一份map,我通过我的key去取值,你通过你的key去取值,互不干预,你用你的我用我的,所以达到线程安全

下面是大神画的图
在这里插入图片描述

四、从ThreadLocalMap看ThreadLocal使用不当的内存泄漏问题

①强引用:Java中默认的引用类型,一个对象如果具有强引用那么只要这种引用还存在就不会被GC。
就是说除非手动 = null 了,要不不回收

②软引用:简言之,如果一个对象具有弱引用,在JVM发生OOM之前(即内存充足够使用),是不会GC这个对象的;只有到JVM内存不足的时候才会GC掉这个对象。软引用和一个引用队列联合使用,如果软引用所引用的对象被回收之后,该引用就会加入到与之关联的引用队列中
就是说GC时内存没地了就会回收

③弱引用(这里讨论ThreadLocalMap中的Entry类的重点):如果一个对象只具有弱引用,那么这个对象就会被垃圾回收器GC掉(被弱引用所引用的对象只能生存到下一次GC之前,当发生GC时候,无论当前内存是否足够,弱引用所引用的对象都会被回收掉)。弱引用也是和一个引用队列联合使用,如果弱引用的对象被垃圾回收期回收掉,JVM会将这个引用加入到与之关联的引用队列中。若引用的对象可以通过弱引用的get方法得到,当引用的对象呗回收掉之后,再调用get方法就会返回null
就是说GC了就把它干掉

④虚引用:虚引用是所有引用中最弱的一种引用,其存在就是为了将关联虚引用的对象在被GC掉之后收到一个通知。(不能通过get方法获得其指向的对象)

先来康康代码,Entry 的key继承了弱引用,也就是说下次GC就把key干掉了

static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

这个大神的图画的挺好,GC时把弱引用key干掉了,map就变成 null - value,大神这个value50M,就一直放在内存里了,当线程数上来了,内存里的无用value堆积多了就OOM(OutOfMemory)了,所以当变量使用完手动remove()整个Entry就好了
在这里插入图片描述

写文章好累啊。。。
佩服那些码几万字还排班工整的大神们,冲鸭。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值