ThreadLocal 详解

概念

ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。

使用示例

1public class ThreadLocalTest { 2 private static String strLabel; 3 private static ThreadLocal<String> threadLabel = new ThreadLocal<>(); 4 5 public static void main(String... args) { 6 strLabel = "main"; 7 threadLabel.set("main"); 8 9 Thread thread = new Thread() { 10 11 @Override 12 public void run() { 13 super.run(); 14 strLabel = "child"; 15 threadLabel.set("child"); 16 } 17 18 }; 19 20 thread.start(); 21 try { 22 // 保证线程执行完毕 23 thread.join(); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 28 System.out.println("strLabel = " + strLabel); 29 System.out.println("threadLabel = " + threadLabel.get()); 30 } 31} 32 33// 运行结果 34// strLabel = child 35// threadLabel = main

ThreadLocal 实现

set方法

1public void set(T value) { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t); 4 if (map != null) 5 map.set(this, value); 6 else 7 createMap(t, value); 8} 9

set(T value) 方法中,首先获取当前线程,然后在获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则将 value 保存到 ThreadLocalMap 中,并用当前 ThreadLocal 作为 key;否则创建一个 ThreadLocalMap 并给到当前线程,然后保存 value。 ThreadLocalMap 相当于一个 HashMap,是真正保存值的地方。

get方法

1public T get() { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t); 4 if (map != null) { 5 ThreadLocalMap.Entry e = map.getEntry(this); 6 if (e != null) { 7 @SuppressWarnings("unchecked") 8 T result = (T)e.value; 9 return result; 10 } 11 } 12 return setInitialValue(); 13} 14

同样的,在 get() 方法中也会获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则把获取 key 为当前 ThreadLocal 的值;否则调用 setInitialValue() 方法返回初始值,并保存到新创建的 ThreadLocalMap 中。

当前线程的 ThreadLocalMap

在 set,get,initialValue 和 remove 方法中都会获取到当前线程,然后通过当前线程获取到 ThreadLocalMap,如果 ThreadLocalMap 为 null,则会创建一个 ThreadLocalMap,并给到当前线程。

构造方法

1ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { 2 table = new Entry[INITIAL_CAPACITY]; 3 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 4 table[i] = new Entry(firstKey, firstValue); 5 size = 1; 6 setThreshold(INITIAL_CAPACITY); 7}

构造方法中会新建一个数组,并将将第一次需要保存的键值存储到一个数组中,完成一些初始化工作。

存储结构

ThreadLocalMap 内部维护了一个哈希表(数组)来存储数据,并且定义了加载因子:

1// 初始容量,必须是 2 的幂 2private static final int INITIAL_CAPACITY = 16; 3 4// 存储数据的哈希表 5private Entry[] table; 6 7// table 中已存储的条目数 8private int size = 0; 9 10// 表示一个阈值,当 table 中存储的对象达到该值时就会扩容 11private int threshold; 12 13// 设置 threshold 的值 14private void setThreshold(int len) { 15 threshold = len * 2 / 3; 16}

假设 Entry 的 key 没有使用弱引用的方式,而是使用了强引用:由于 ThreadLocalMap 的生命周期和当前线程一样长,那么当引用 ThreadLocal 的对象被回收后,由于 ThreadLocalMap 还持有 ThreadLocal 和对应 value 的强引用,ThreadLocal 和对应的 value 是不会被回收的,这就导致了内存泄漏。所以 Entry 以弱引用的方式避免了 ThreadLocal 没有被回收而导致的内存泄漏,但是此时 value 仍然是无法回收的,依然会导致内存泄漏。

ThreadLocalMap 已经考虑到这种情况,并且有一些防护措施:在调用 ThreadLocal 的 get(),set() 和 remove() 的时候都会清除当前线程 ThreadLocalMap 中所有 key 为 null 的 value。这样可以降低内存泄漏发生的概率。所以我们在使用 ThreadLocal 的时候,每次用完 ThreadLocal 都调用 remove() 方法,清除数据,防止内存泄漏。

文章有帮助你,请关注微信公众号:肆意游离 有更多精彩等着你

点赞
收藏

评论区

加载中...

相关推荐

spring中,在Java任何位置获取request对象

看RequestContextListener和RequestContextHolder源代码不难看出,是用过ThreadLocal实现的。ThreadLocal是什么呢?看词义,它并非是一个线程,它不是一个Thread,而是一个线程局部变量,其实功能非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而

java ThreadLocal

ThreadLocal是什么定义:提供线程局部变量;一个线程局部变量在多个线程中,分别有独立的值(副本)特点:简单(开箱即用)、快速(无额外开销)、安全(线程安全)场景:多线程场景(资源持有、线程一致性、并发计算、线程安全等场景)ThreadLocal基本API 构

ThreadLocal源码解析及实战应用

ThreadLocal是一个关于创建线程局部变量的类。通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。ThreadLocal在设计之初就是为解决并发问题而提供一种方案,每个线程维护一份自己的数据,达到线程隔离的效果。

ThreadLocal源码分析

最近在学多线程并发的知识,发现好像ThreadLoca还挺重要,决定看看源码以及查找各方资料来学习一下。ThreadLocal能够提供线程的局部变量,让每个线程都可以通过set/get来对这个局部变量进行操作,不会和其它线程的局部变量进行冲突,实现了线程的数据隔离。首先是ThreadLocal的结构:每个Thread维护一个ThreadLocalMap,这个

最常见的java面试题汇总

1.什么是线程局部变量?(答案)线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如web服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何

Java ThreadLocal的内存泄漏问题

ThreadLocal提供了线程独有的局部变量,可以在整个线程存活的过程中随时取用,极大地方便了一些逻辑的实现。常见的ThreadLocal用法有:\存储单个线程上下文信息。比如存储id等;\使变量线程安全。变量既然成为了每个线程内部的局部变量,自然就不会存在并发问题了;\减少参数传递。比如做一个trace工具,能够输出工程从开始到结