ThreadLocal

简介

多线程访问同一个共享变量的时候容易出现并发问题,ThreadLocal是除了加锁外的一种规避多线程不安全的方法。

ThreadLocal是JDK包提供的,它提供线程本地变量,每个线程都会有变量的一个副本,访问的都是线程自己的变量副本,从而规避了线程安全问题,如下图所示

使用

  • void set(Object value):设置当前线程的线程局部变量的值。
  • public Object get():该方法返回当前线程所对应的线程局部变量。
  • public void remove():将当前线程局部变量的值删除,(非必须)线程结束后局部变量将自动被垃圾回收。
  • protected Object initialValue():返回该线程局部变量的初始值,在线程第1次调用get()或set(Object)时才延迟执行,且仅执行1次。
public class ThreadLocalTest {
​
    static ThreadLocal<String> localVar = new ThreadLocal<>();
​
    static void print(String str) {
        //打印当前线程中本地内存中本地变量的值
        System.out.println(str + " :" + localVar.get());
        //清除本地内存中的本地变量
        localVar.remove();
    }
​
    public static void main(String[] args) {
        Thread t1  = new Thread(new Runnable() {
            @Override
            public void run() {
                //设置线程1中本地变量的值
                localVar.set("localVar1");
                //调用打印方法
                print("thread1");
                //打印本地变量
                System.out.println("after remove : " + localVar.get());
            }
        });
​
        Thread t2  = new Thread(new Runnable() {
            @Override
            public void run() {
                //设置线程1中本地变量的值
                localVar.set("localVar2");
                //调用打印方法
                print("thread2");
                //打印本地变量
                System.out.println("after remove : " + localVar.get());
            }
        });
​
        t1.start();
        t2.start();
        
        // thread1 :localVar1
        // after remove : null
        // thread2 :localVar2
        // after remove : null
​
    }
}

原理

每个线程都维护了一个ThreadLocalMap,而这个map的key就是threadLocal,value就是set存储的对象。

ThreadLocalMap

set方法

//Entry为ThreadLocalMap静态内部类,对ThreadLocal的若引用
//同时让ThreadLocal和储值形成key-value的关系
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
​
    Entry(ThreadLocal<?> k, Object v) {
           super(k);
            value = v;
    }
}
​
//ThreadLocalMap构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        //内部成员数组,INITIAL_CAPACITY值为16的常量
        table = new Entry[INITIAL_CAPACITY];
        //位运算,结果与取模相同,计算出需要存放的位置
        //threadLocalHashCode比较有趣
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
}
​

实例化ThreadLocalMap时创建了一个长度为16的Entry数组。每个线程Thread持有一个ThreadLocalMap类型的实例threadLocals,可以理解成每个线程Thread都持有一个Entry型的数组table。

通过hashCode与length位运算确定出一个索引值i,这个i就是被存储在table数组中的位置。

ThreadLocal

//在某一线程声明了ABC三种类型的ThreadLocal
ThreadLocal<A> sThreadLocalA = new ThreadLocal<A>();
ThreadLocal<B> sThreadLocalB = new ThreadLocal<B>();
ThreadLocal<C> sThreadLocalC = new ThreadLocal<C>();

一个Thread来说只有持有一个ThreadLocalMap,所以ABC三个ThreadLocal对应同一个ThreadLocalMap对象。

为了管理ABC,通过将threadLocal的hashCode进行位运算(取模)得到索引i,将ABC存储在一个数组的不同位置,而这个数组就是上面提到的Entry型的数组table。

总结如下:

  1. 对于某一ThreadLocal来讲,他的索引值i是确定的,在不同线程之间访问时访问的是不同的table数组的同一位置即都为table[i],只不过这个不同线程之间的table是独立的。
  2. 对于同一线程的不同ThreadLocal来讲,这些ThreadLocal实例共享一个table数组,然后每个ThreadLocal实例在table中的索引i是不同的。

应用场景

保存每个线程独享的对象

ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本,确保了线程安全。

保存线程内用户信息

ThreadLocal 用作每个线程内需要独立保存信息,以便供其他方法更方便地获取该信息的场景。每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念。

例如,用 ThreadLocal 保存一些业务内容(用户权限信息、从用户系统获取到的用户名、用户ID 等),这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的。

在线程生命周期内,都通过这个静态 ThreadLocal 实例的 get() 方法取得自己 set 过的那个对象,避免了将这个对象(如 user 对象)作为参数传递的麻烦。

避免线程内多次获取数据库连接、Session 管理等

在数据库连接上的应用

ThreadLocal和Synchronized区别

  • Synchronized是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

ThreadLocal导致内存泄漏

内存溢出: Memory overflow 没有足够的内存提供申请者使用.

内存泄漏: Memory Leak 程序中已经动态分配的堆内存由于某种原因, 程序未释放或者无法释放, 造成系统内部的浪费, 导致程序运行速度减缓甚至系统崩溃等严重结果。内存泄漏的堆积终将导致内存溢出。

key=ThreadLocal是强引用行不行?

假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了。

但是因为threadLocalMap的Entry强引用了threadLocal(key就是threadLocal),造成ThreadLocal无法被回收。

在没有手动删除Entry以及CurrentThread(当前线程)依然运行的前提下, 始终有强引用链CurrentThread Ref → CurrentThread →Map(ThreadLocalMap)-> entry, Entry就不会被回收( Entry中包括了ThreadLocal实例和value),导致Entry内存泄漏。

key=ThreadLocal是弱引用还会内存泄漏吗

假设在业务代码中使用完ThreadLoca, ThreadLocal ref被回收了。

由于threadLocalMap只持有ThreadLocal的弱引用,没有任何强引用指向threadlocal实例(这里Entry不再强引用ThreadLocal了), 所以threadlocal就可以顺利被gc回收, 此时Entry中的key = null。

在没有手动删除Entry以及CurrentThread依然运行的前提下,也存在始终有强引用链CurrentThread Ref → CurrentThread →Map(ThreadLocalMap)-> entry,value就不会被回收,而这块value永远不会被访问到了(因为key=null),导致value内存泄漏。

为什么 key 要用弱引用

两种内存泄漏的情况中.都有两个前提:

1 . 没有手动侧除这个 Entry

2 . CurrentThread 当前线程依然运行

综上, ThreadLocal 内存泄漏的根源是:

由于ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除(remove()方法)对应 key 就会导致内存泄漏。

要避免内存泄漏有两种方式:

  • 1 .使用完 ThreadLocal ,调用其 remove 方法删除对应的 Entry
  • 2 .使用完 ThreadLocal ,当前 Thread 也随之运行结束

那么为什么 key 要用弱引用呢

事实上,在 ThreadLocalMap 中的set/getEntry 方法中,会对 key 为 null(也即是 ThreadLocal 为 null )进行判断,如果为 null 的话,那么会把 value 置为 null 的。

这就意味着使用完 ThreadLocal,CurrentThread 依然运行的前提下。就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收。对应value在下一次 ThreadLocaIMap 调用 set/get/remove 中的任一方法的时候会被清除,从而避免内存泄漏。

参考:

ThreadLocal:https://www.jianshu.com/p/3c5d7f09dfbd

Java中的ThreadLocal详解:https://www.cnblogs.com/fsmly/p/11020641.html

ThreadLocal 内存泄露问题:https://blog.csdn.net/JH39456194/article/details/107304997

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • ThreadLocal

    一、定义   翻译成中文应该叫做线程局部变量。这个类到底有什么用处呢?在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,...

    用户1195962
  • ThreadLocal

    当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本

    黑洞代码
  • ThreadLocal

    例如,以下类生成对每个线程唯一的局部标识符。 线程 ID 是在第一次调用 UniqueThreadIdGenerator.getCur

    乐事
  • ThreadLocal

    通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK中提供的ThreadLocal类正是...

    崔笑颜
  • ThreadLocal

    ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状...

    earthchen
  • Java ThreadLocal

    Tencent JCoder
  • ThreadLocal 类

    ThreadLocal 并不是一个Thread,而是 ThreadLocalVariable(线程局部变量)。也许把它命名为 ThreadLocalVar更加合...

    Java架构师必看
  • threadlocal记录

    https://www.juejin.im/post/6844903586984361992

    猎户星座1
  • ThreadLocal解析

    这个类,好早就有了,JDK1.2就出现了。有时也会用一用,但他的作用是什么,很难表达了,难以表达,不能形成文字,说明了解的深度不够。

    码农戏码
  • ThreadLocal介绍

    ThreadLocal是一个关于创建线程局部变量的类。通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当...

    HLee
  • ThreadLocal原理

    ThreadLocal 是线程的局部变量, 是每一个线程所单独持有的,其他线程不能对其进行访问。

    用户6182664
  • ThreadLocal分析

    ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状...

    爱撸猫的杰
  • 讲透ThreadLocal

    ThreadLocal是JDK提供的一个工具类,其作用是在多线程共享资源的情况下,使每个线程持有一份该资源的副本,每个线程的副本都是独立互不影响的。线程操作各自...

    张申傲
  • 揭秘ThreadLocal

    ThreadLocal是开发中最常用的技术之一,也是面试重要的考点。本文将由浅入深,介绍ThreadLocal的使用方式、实现原理、内存泄漏问题以及使用场景。...

    大闲人柴毛毛
  • 浅析ThreadLocal

    上图可以看出ThreadLocal类中通过ThreadLocalMap去存储,ThreadLocalMap中的存储结构为Entry数组.

    LiosWong
  • ThreadLocal 原理

    从上面可以看出,Thread 类中有一个 threadLocals 和 inheritableThreadLocals 变量,它们都是 ThreadLocalM...

    happyJared
  • ThreadLocal详解

      这个玩意有什么用处,或者说为什么要有这么一个东东?先解释一下,在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然...

    lyb-geek
  • threadLocal 记录

    翻译成中文应该叫做线程局部变量。这个类到底有什么用处呢?在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,...

    砍鸡鸡
  • ThreadLocal概述

    ThreadLocal 是解决多线程并发问题时一个常用的且有效的方法。

    Coder的技术之路

扫码关注云+社区

领取腾讯云代金券