概述

ThreadLocal 是 java 提供的一个方便对象在本线程内不同方法中传递和获取的类。用它定义的变量,仅在本线程中可见和维护,不受其他线程的影响,与其他线程相互隔离。

虽然在本线程不同方法中使用变量,可以通过在方法中传入参数解决,但是当涉及多个方法甚至多个类时,为每个方法增加同样的参数将是一场噩梦,此时 ThreadLocal 就能很好地解决这个问题。它可以在本线程内任何一个地方赋值,在任何一个地方获取值,并且不用作为函数参数传入。这看起来像静态成员变量,但是 ThreadLocal 变量相比静态成员变量的一个优势就是,ThreadLocal 是线程隔离的,其值不会受另一个线程的影响,也不用考虑加锁或值被其他线程篡改的问题,而这些问题都是静态成员变量无法做到的。因此当涉及一个对象需要在很多不同方法之间传递时,应该考虑使用 ThreadLocal 对象来简化代码。

使用

ThreadLocal 通过 set 方法可以给变量赋值,通过 get 方法获取变量的值。当然,也可以在定义变量时通过 ThreadLocal.withInitial 方法给变量赋初始值,或者定义一个继承 ThreadLocal 的类,然后重写 initialValue 方法。

示例代码如下

public class TestThreadLocal

{

private static ThreadLocal builder = ThreadLocal.withInitial(StringBuilder::new);

public static void main(String[] args)

{

for (int i = 0; i < 5; i++)

{

new Thread(() -> {

String threadName = Thread.currentThread().getName();

for (int j = 0; j < 3; j++)

{

append(j);

System.out.printf("%s append %d, now builder value is %s, ThreadLocal instance hashcode is %d, ThreadLocal instance mapping value hashcode is %d\n", threadName, j, builder.get().toString(), builder.hashCode(), builder.get().hashCode());

}

change();

System.out.printf("%s set new stringbuilder, now builder value is %s, ThreadLocal instance hashcode is %d, ThreadLocal instance mapping value hashcode is %d\n", threadName, builder.get().toString(), builder.hashCode(), builder.get().hashCode());

}, "thread-" + i).start();

}

}

private static void append(int num) {

builder.get().append(num);

}

private static void change() {

StringBuilder newStringBuilder = new StringBuilder("HelloWorld");

builder.set(newStringBuilder);

}

}

在例子中,定义了一个 builder 的 ThreadLocal 对象,然后启动 5 个线程,分别对 builder 对象进行访问和修改操作,这两个操作放在两个不同的函数 append、change 中进行,两个函数访问 builder 对象也是直接获取,而不是放入函数的入参中传递进来。

代码输出如下

thread-0 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 566157654

thread-0 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 566157654

thread-4 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 654647086

thread-3 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1803363945

thread-2 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1535812498

thread-1 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 2075237830

thread-2 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1535812498

thread-3 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1803363945

thread-4 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 654647086

thread-0 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 566157654

thread-0 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1773033190

thread-4 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 654647086

thread-4 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 700642750

thread-3 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1803363945

thread-3 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1706743158

thread-2 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1535812498

thread-2 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1431127699

thread-1 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 2075237830

thread-1 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 2075237830

thread-1 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1970695360

从输出中 1~6 行可以看出,不同线程访问的是同一个 builder 对象(不同线程输出的 ThreadLocal instance hashcode 值相同),但是每个线程获得的 builder 对象存储的实例 StringBuilder 不同(不同线程输出的 ThreadLocal instance mapping value hashcode 值不相同)。

从输出中 1~2、9~10行可以看出,同一个线程中修改 builder 对象存储的实例的值时,并不会影响到其他线程的 builder 对象存储的实例(thread-4 线程改变存储的 StringBuilder 的值并不会引起 thread-0 线程的 ThreadLocal instance mapping value hashcode 值发生改变)

从输出中 9~13 行可以看出,一个线程对 ThreadLocal 对象存储的值发生改变时,并不会影响其他的线程(thread-0 线程调用 set 方法改变本线程 ThreadLocal 存储的对象值,本线程的 ThreadLocal instance mapping value hashcode 发生改变,但是 thread-4 的 ThreadLocal instance mapping value hashcode 并没有因此改变)。

原理

ThreadLocal 能在每个线程间进行隔离,其主要是靠在每个 Thread 对象中维护一个 ThreadLocalMap 来实现的。因为是线程中的对象,所以对其他线程不可见,从而达到隔离的目的。那为什么是一个 Map 结构呢。主要是因为一个线程中可能有多个 ThreadLocal 对象,这就需要一个集合来进行存储区分,而用 Map 可以更快地查找到相关的对象。

ThreadLocalMap 是 ThreadLocal 对象的一个静态内部类,内部维护一个 Entry 数组,实现类似 Map 的 get 和 put 等操作,为简单起见,可以将其看做是一个 Map,其中 key 是 ThreadLocal 实例,value 是 ThreadLocal 实例对象存储的值。

set

当调用 ThreadLocal 的 set 方法给变量设置值时,ThreadLocal 对象会先获取本线程的 ThreadLocalMap 对象,然后将当前的 ThreadLocal 对象及要设置值作为键值对放入 Map 中。

public void set(T value) {

Thread t = Thread.currentThread();

// 获取当前线程的 ThreadLocalMap 对象

ThreadLocalMap map = getMap(t);

if (map != null)

// this 指当前的 ThreadLocal 对象

map.set(this, value);

else

// key 不存在,则创建 map 并设置值

createMap(t, value);

}

ThreadLocalMap getMap(Thread t) {

// threadLocals 是 Thread 中的一个变量,因此是线程隔离的,不会受其他线程影响

// 其在 Thread 类中的定义如下:ThreadLocal.ThreadLocalMap threadLocals = null;

return t.threadLocals;

}

get

获取 ThreadLocal 存储的对象值时,需要调用 get 方法。此方法也是先获取本线程的 ThreadLocalMap 对象,然后将当前的 ThreadLocal 对象作为 key 从 Map 中获取对应的值,如果没有,则返回一个初始 null。

public T get() {

Thread t = Thread.currentThread();

// 获取当前线程的 ThreadLocalMap 对象

ThreadLocalMap map = getMap(t);

if (map != null) {

// this 指当前的 ThreadLocal 对象

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings("unchecked")

T result = (T)e.value;

return result;

}

}

return setInitialValue();

}

内存泄漏

ThreadLocalMap 中的 key 是一个 ThreadLocal 对象,且是一个弱引用,而 value 却是一个强引用。

static class ThreadLocalMap {

/**

* The entries in this hash map extend WeakReference, using

* its main ref field as the key (which is always a

* ThreadLocal object). Note that null keys (i.e. entry.get()

* == null) mean that the key is no longer referenced, so the

* entry can be expunged from table. Such entries are referred to

* as "stale entries" in the code that follows.

*/

static class Entry extends WeakReference> {

/** The value associated with this ThreadLocal. */

Object value;

Entry(ThreadLocal> k, Object v) {

super(k);

value = v;

}

}

// 其他代码

}

毫无疑问,如果线程执行完关闭,那么线程的所有对象都会被销毁,此时不会存在内存泄漏的问题。此外,在执行 get、set 操作时,调用进入 ThreadLocalMap 内部的函数,会对 Entry 进行检查,如果 key 为空,也会将 value 设置为空,让其可以被垃圾回收。所以一般情况下也不会造成内存泄漏。

// get 或 set 方法,满足一定条件时会进入 expungeStaleEntry 方法

// 此方法内部会将 key 为 null 的 Entry 的 value 设置为 null,从而使得其可以被垃圾回收

private int expungeStaleEntry(int staleSlot) {

Entry[] tab = table;

int len = tab.length;

// 设置 value 值为 null,清空引用,让其可以被 GC 回收

// expunge entry at staleSlot

tab[staleSlot].value = null;

tab[staleSlot] = null;

size--;

// Rehash until we encounter null

Entry e;

int i;

for (i = nextIndex(staleSlot, len);

(e = tab[i]) != null;

i = nextIndex(i, len)) {

ThreadLocal> k = e.get();

if (k == null) {

// 设置 value 值为 null,清空引用,让其可以被 GC 回收

e.value = null;

tab[i] = null;

size--;

} else {

int h = k.threadLocalHashCode & (len - 1);

if (h != i) {

tab[i] = null;

// Unlike Knuth 6.4 Algorithm R, we must scan until

// null because multiple entries could have been stale.

while (tab[h] != null)

h = nextIndex(h, len);

tab[h] = e;

}

}

}

return i;

}

但是,存在一种情况,可能导致内存泄漏。如果在某一时刻,将 ThreadLocal 实例设置为 null,即没有 ThreadLocal 没有强引用了,如果发生 GC 时,由于 ThreadLocal 实例只存在弱引用,所以被回收了,但是 value 仍然存在一个当前线程连接过来的强引用,其不会被回收,只有等到线程结束死亡或者手动清空 value 或者等到另一个 ThreadLocal 对象进行 get 或 set 操作时刚好触发 expungeStaleEntry 函数并且刚好能够检查到本 ThreadLocal 对象 key 为空(概率太小),这样才不会发生内存泄漏。否则,value 始终有引用指向它,它也不会被 GC 回收,那么就会导致内存泄漏。虽然发生内存泄漏的概率比较小,但是为了保险起见,也建议在使用完 ThreadLocal 对象后调用一下 remove 方法清理一下值。

与线程池结合使用

由于线程池是会复用线程的,因此如果在线程任务中对 ThreadLocal 没有经过重新设值而直接读取值的话,可能读取到的是该线程上一个任务赋值的结果,而不是本次任务的初始值,从而导致一些意向不到的错误。如下所示,创建一个固定大小是 3 的线程池,但是往线程池中放入 5 个任务,则最后两个任务会复用之前创建的线程,此时调用 ThreadLocal 的 get 方法获取到的是上一个任务赋值的结果,而不是本线程的初始值(程序输出的第4~5 行就是复用了线程 11 和 13,第一次获取到的是也是上一个任务赋的值 2,而不是本线程的初始值 1)。

public class TestThreadLocalExecutor

{

private static ThreadLocal id = ThreadLocal.withInitial(() -> 1);

public static void main(String[] args)

{

ExecutorService executor = Executors.newFixedThreadPool(3);

for (int i = 0; i < 5; i++)

{

executor.execute(() -> {

long threadId = Thread.currentThread().getId();

// 任务开始时重新赋值,否则可能读取到的是上一个任务的值

// id.set(1);

int before = id.get();

increment();

int after = id.get();

System.out.printf("Thread id: %d, before increment: %d, after increment: %d\n", threadId, before, after);

});

}

executor.shutdown();

}

private static void increment()

{

int result = id.get() + 1;

id.set(result);

}

}

程序输出如下

Thread id: 11, before increment: 1, after increment: 2

Thread id: 13, before increment: 1, after increment: 2

Thread id: 12, before increment: 1, after increment: 2

Thread id: 13, before increment: 2, after increment: 3

Thread id: 11, before increment: 2, after increment: 3

为了避免如上情况的发生,可以在每个任务开始时,为 ThreadLocal 对象重新设置初始值(在 get 方法前先调用 set 方法),或者使用原生的创建线程的方式(跳开线程池的方式)。

threadlocal存连接对象的目的_ThreadLocal 介绍相关推荐

  1. threadlocal存连接对象的目的_面试官:知道ThreadLocal嘛?谈谈你对它的理解?

    在java的多线程模块中,ThreadLocal是经常被提问到的一个知识点,提问的方式有很多种,可能是循序渐进也可能是就像我的题目那样,因此只有理解透彻了,不管怎么问,都能游刃有余. 这篇文章主要从以 ...

  2. threadlocal存连接对象的目的_终于懂了ThreadLocal,不再害怕面试官问了

    ThreadLocal解析 synchronized和ThreadLocal的区别: synchronized:以时间换空间,只提供一份变量,让不同的线程排队访问,失去了并发性,降低了程序效率,着重对 ...

  3. Spring JDBC-使用Spring JDBC获取本地连接对象以及操作BLOB/CLOB类型数据

    概述 如何获取本地数据连接 示例从DBCP数据源中获取Oracle的本地连接对象 相关接口操作 LobCreator LobHandler 插入LOB类型的数据 以块数据的方式读取LOB数据 以流数据 ...

  4. Java 数据对象(JDO)介绍

      Java 数据对象(JDO)介绍 by Jeff Brown, Senior Software Engineer Object Computing, Inc. (OCI) 介绍 Java 数据对象 ...

  5. SQL优化技巧--远程连接对象引起的CTE性能问题

    背景 最近SSIS的开发过程中遇到几个问题.其中使用CTE时,遇到一个远程连接对象,结果导致严重的性能问题,为了应急我就修改了代码. 之前我写了一篇介绍CTE的随笔包含了CTE的用法等: http:/ ...

  6. COM原理及应用之可连接对象

    1.COM的高级特性 COM规范中有一些高级特性,如可连接对象.永久存储.一致的数据传输等,使COM规范具有更强的生命力,它们也是OLE的基础,但它们的应用又不仅仅局限于OLE,这些高级特性已经广泛应 ...

  7. JDBC工具类,基于C3P0的数据库连接池,提供获取连接池、获取连接对象、释放资源和封装事务操作的方法...

    /**  *  * JDBC工具类,基于C3P0数据库连接池的实现  *  * @author 周瑜  * @2018年5月7日 下午2:13:20  */ public final class JD ...

  8. 工业级加固固态硬盘产品性能-领存2.5 寸 R-SATA SSD介绍

    工业级加固固态硬盘产品性能-领存2.5 寸 R-SATA SSD介绍 Z3 系列 2.5 寸 R-SATA 工业级固态硬盘(简称 Z3 R-SATA 盘)一款针对工业.汽车电子.医疗设备.航空航海等特 ...

  9. 2.1.3 客户端网络连接对象

    2.1.3 客户端网络连接对象 客户端网络连接对象(NetworkClient )管理了客户端和服务端之间的 网络通信,包括连接的建立.发送客户端请求 . 读取客户端响应.回顾下2 . 1.2节中第 ...

最新文章

  1. 算法:详解布隆过滤器的原理、使用场景和注意事项@知乎.Young Chen
  2. iOS单独集成QQ分享功能
  3. Nature Microbiology:肠道菌群如何划分肠型
  4. Mac卸载mysql并安装mysql升级到8.0.13版本
  5. r4卡2020整合内核_R4卡使用方法!游戏介绍及常见问题!任天堂掌机通用
  6. slf4j 和 log4j的关系及合用Maven配置
  7. linux执行jar包命令没有主清单熟悉,jar命令成功完成 java -jar 命令却提示“没有主清单属性”!...
  8. Java读取多层级xml文件
  9. swiper移入暂停_react中swiper注意事项及鼠标划入停止轮播
  10. STM32之通用定时器编码器模式
  11. 领导给了一堆无序杂乱的数据,我写了个Python自动化脚本
  12. 关于C#关闭窗体后,依旧有后台进程在运行的解决方法
  13. 转:12种JavaScript MVC框架之比较
  14. 浅谈 HTTPS 和 SSL -TLS 协议的背景与基础
  15. JQuery iframe页面通过parent方法操作父页面中的元素与方法(实例讲解)
  16. BGP选择路由的策略
  17. sai厚涂上色教程,sai厚涂绘画人物上色教程!
  18. 【第十届“泰迪杯”数据挖掘挑战赛】C题:疫情背景下的周边游需求图谱分析 赛后总结、46页论文及代码
  19. Mac OS X 10.7.x Lion下添加离线词典
  20. 图像直方图以及直方图的应用

热门文章

  1. JZOJ 1237. 餐桌
  2. JAVA基础知识(五)数据类型转换
  3. OsharpNS轻量级.net core快速开发框架简明入门教程-基于Osharp实现自己的业务功能...
  4. hdu 2191 (多重背包二进制优化)
  5. 用toString生成随机字符
  6. oracle本地安装注意事项
  7. Exception异常处理
  8. JAVA08 多态
  9. UIApplication Delegate和UIApplicationMain(程序完整启动过程)
  10. 解决Hibernate4执行update操作,不更新数据的问题