深入理解并发编程之ThreadLocal原理分析

深入理解并发编程之ThreadLocal原理分析


一、什么是ThreadLocal

ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定。简单的说就是ThreadLocal为多个线程提供了互不干扰的本地变量。
Threadlocal适用于在多线程的情况下,可以实现传递数据,实现线程隔离。

Thread有三个核心方法:

  • set(): 设置当前线程绑定的局部变量。
  • get(): 获取当前线程绑定的局部变量。
  • remove(): 移除当前线程绑定的变量。

简单的来看一个ThreadLocal的使用Demo:

public class Test001 {
    private static ThreadLocal threadLocal = new ThreadLocal<String>();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                threadLocal.set("这是线程"+Thread.currentThread().getName());
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }).start();
        }
        threadLocal.set("这里是主线程");
        System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
    }
}

通过打印结果我们可以看出来,线程之间并不会出现打印错乱的并发问题,没有出现例如线程1可能打印到线程2的情况:
在这里插入图片描述

ThreadLocal的使用场景是比较多的,例如:

  • Spring的数据库事务模板用来缓存对应的数据库连接。
  • SpringMVC获取HttpRequest对象,Spring将HttpRequest对象都缓存到对应的线程中就是使用的ThreadLocal。

二、ThreadLocal原理分析

ThreadLocalMap

Thread(注意这里是Thread而不是ThreadLocal)有个核心属性ThreadLocalMap threadLocals,这是每个线程都有一份的,他们之间相互独立隔离。
下面是ThreadLocalMap的UML图:
在这里插入图片描述
看图其实和Map差不多的,这里主要是分析ThreadLocal的,对于ThreadLocalMap具体是怎么存储的不做深究了,知道是通过ThreadLocal的哈希值来计算数组存放位置来存放获取变量的就行,可以看出是通过Entry数组来保存局部变量的,再根据我们对Map的了解,看一下ThreadLocalMap的get()和set()相关的方法可以看出,它的key是ThreadLocalValue是我们实际存放的变量。其实这里曾经有过一个面试题:为什么线程缓存的是ThreadLocalMap对象而不是ThreadLocal?,其实真正使用过ThreadLocal就知道答案非常简单,因为一个线程有使用的不止有一个ThreadLocal,所以我们要把它存放在Map中,通过ThreadLocal来存取对应的值。

set()方法

    public void set(T value) {
        Thread t = Thread.currentThread(); 
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value); //this就是指的当前的ThreadLocal
        else
            createMap(t, value);
    }

看到ThreadLocal的set()源码非常简单,首先获取当前线程,然后获取当前线程自己的Map,最后使用当前ThreadLocal作为Key往Map里面赋值

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

get()方法也是非常简单的,就是获取当前线程,然后获取当前线程的ThreadLocalMap,最后获取ThreadLocalMap中Key为当前ThreadLocal的value值

三、ThreadLocal涉及的问题

ThreadLocal的内存泄漏问题

先说几个词:内存泄漏、内存溢出、强引用、 软引用、 弱引用和虚引用。

内存泄漏:指程序在申请内存后,无法释放已申请的内存空间,内存泄露堆积会导致内存被占光,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
内存溢出:指程序在申请内存时,没有足够的内存空间供其使用,出现 out of memory, OOM溢出,程序在申请内存时,没有足够的内存空间使用,一般解决办法:加内存。
强引用:被引用关联的对象永远不会被垃圾收集器回收,Object obj = new Object()。
软引用:软引用关联的对象,只有当系统内存溢出时,才会回收软引用的对象,SoftReference objRef = new SoftReference(obj)。
弱引用:只被弱引用关联的对象,当垃圾回收机制触发的时候就会被回收,WeakReference objRef = new WeakReference(obj),ThreadLocal就是使用的弱引用。
虚引用:它就和没有任何引用一样,在任何时候都可能被垃圾回收,通过PhantomReference来实现。

为什么ThreadLocal会有内存泄漏问题?
先看下面一段测试代码:

public class Test001 {
    private static ThreadLocal threadLocal = new ThreadLocal<String>();

    public static void main(String[] args) throws InterruptedException {
        threadLocal.set("测试");
        threadLocal = null;
        System.gc();
        Thread thread = Thread.currentThread(); //在这一行打断点看一下
        System.out.println("打印断点");
    }
}

看一下断点打印,我们可以看到Thread里面的ThreadLocal并没有被清理:
在这里插入图片描述

出现这个现象的原因很简单
ThreadLocalMap使用ThreadLocal的弱引用作为key,上面我们说了弱引用会在垃圾回收的时候清除掉,虽然ThreadLocalMap中key为null,但是而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉。我们看上面的打印结果就看到了ref已经为null了,但是value还是存在的。

解决办法:

  • 可以自己调用remove()方法将不要的数据移除避免内存泄漏的问题。
  • 每次在做set方法的时候会清除之前key为null,ThreadLocalMap因为每次调用set方法的时候,会判断如果key空的情况下,直接删除。

弱引用存在内存泄漏问题为啥还要用弱引用

首先,如果使用强引用的话,即使我们把ThreadLocal设置为null了,ThreadLocalMap的ThreadLocal还会继续存在,这样是肯定会存在内存泄漏问题的,而弱引用是可能出现内存泄漏问题,我们处理得当可以避免的;而软引用更不可能了,软引用得内存溢出才会回收的。

内容来源:蚂蚁课堂

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值