前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ThreadLocal

ThreadLocal

作者头像
chenchenchen
发布2021-09-06 14:26:21
3860
发布2021-09-06 14:26:21
举报
文章被收录于专栏:chenchenchenchenchenchen

简介

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

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

img
img

使用

  • void set(Object value):设置当前线程的线程局部变量的值。
  • public Object get():该方法返回当前线程所对应的线程局部变量。
  • public void remove():将当前线程局部变量的值删除,(非必须)线程结束后局部变量将自动被垃圾回收。
  • protected Object initialValue():返回该线程局部变量的初始值,在线程第1次调用get()或set(Object)时才延迟执行,且仅执行1次。
代码语言:javascript
复制
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方法

代码语言:javascript
复制
//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

代码语言:javascript
复制
//在某一线程声明了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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-05-20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 使用
  • 原理
    • ThreadLocalMap
      • ThreadLocal
        • 保存每个线程独享的对象
        • 保存线程内用户信息
    • 应用场景
      • 避免线程内多次获取数据库连接、Session 管理等
        • ThreadLocal和Synchronized区别
        • ThreadLocal导致内存泄漏
          • key=ThreadLocal是强引用行不行?
            • key=ThreadLocal是弱引用还会内存泄漏吗
              • 为什么 key 要用弱引用
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档