大家好,我是小黑,一个在互联网苟且偷生的农民工。

从前上一期【并发编程之:synchronized】 我们学到要保证在并发情况下对于共享资源的安全访问,就需要用到锁。

但是,加锁通常情况下会让运行效率降低,那有什么办法可以彻底避免对共享资源的竞争,同时又可以不影响效率呢?答案就是小黑今天要和大家讲的ThreadLocal。

ThreadLocal是什么?

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

以上来源官方API。大概可以总结为两点:

  1. ThreadLocal提供get/set方法,可以访问属于当前线程的变量,也就是可以保证每个线程的变量不一样。
  2. ThreadLocal使用时通常定义为private static的。

从字面意思理解,可能会将ThreadLocal认为是本地线程,其实ThreadLocal并不是线程,而是线程Thread的局部变量。

如何使用

首先,定义一个private static的ThreadLocal对象。

private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

然后每个线程可以将当前线程需要存放在局部变量中,并且可以从中获取。

public void setAndGet(String name) {
threadLocal.set(name);
String s = threadLocal.get();
}

最后在使用完之后,需要将ThreadLocal中的值移除。

public void remove() {
threadLocal.remove();
}

原理

那么ThreadLocal是如何做到保证每个线程get出来的数据不一样的呢?我们通过源码来看一下。

public void set(T value) {
Thread t = Thread.currentThread();
// 通过当前线程获取出来一个ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

我们发现在set方法中,会创建一个ThreadLocalMap,然后将要设置的值放在这个Map中,而当前这个ThreadLocal对象作为key;

然后再将这个ThreadLocalMap赋值给Thread的threadLocals里。如果去看Thread类的代码会发现,在Thread类中存在两个变量threadLocals和inheritableThreadLocals,它们的类型就是ThreadLocal.ThreadLocalMap。通过下图可以看出Thread,ThreadLocal,ThreadLocalMap三者的关系。

这时候我们再看看get()和remove()方法的代码。

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();
} ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
} public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

可以看出来,和我们上面图中的结构一样,get()方法就是从Thread中取出来ThreadLocalMap,然后通过ThreadLocal对象作为Key取出值;remove()方法则是取出ThreadLocalMap将ThreadLocal对应的数据移除。

那么ThreadLocalMap到底是什么呢?是java.util.Map接口的子类吗?我们来看源码。

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold; // Default to 0
// 省略其他代码
}

通过源码发现,ThreadLocalMap并没有实现Map接口,也没有集成其他任何的Map类。是定义在ThreadLocal类中的一个静态内部类。而它的结构和HashMap结构极其相似。有一个Entry[]数组存放数据。而这个Entry类是继承自WeakReference类的子类,这一点和HashMap有所不同。

ThreadLocalMap和HashMap结构有很多不同,但是还有一点和HashMap不一样,我们来看一下ThreadLocalMap的set方法。

private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 计算key对应放在tab中的下标
int i = key.threadLocalHashCode & (len-1); //循环遍历tab,首先获取到下标i对应的对象,如果不为空,则执行循环体
// 如果不是相同的threadLocal或者位置失效,则要寻找下一个位置。
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 判断与当前ThreadLocal是否是同一个对象,如果是则进行值替换,结束
if (k == key) {
e.value = value;
return;
}
// 如果k==null代表threadLocal的key失效了,将失效的key进行替换
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
} tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

代码中循环遍历tab,首先获取到下标i对应的对象,如果不为空,则执行循环体,如果在循环体中的两个条件都不满足,则会执行nextIndex()方法,这个方法的代码如下:

private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}

这个方法的逻辑就是开放寻址法。而HashMap则是通过拉链法rehash来做的。

哪些场景使用

通过上面的内容基本可以掌握ThreadLocal的基本用法,那么ThreadLocal主要在什么场景中使用呢。

ThreadLocal的作用通过以上了解我们知道主要是用来做线程间数据隔离。那么在什么场景下能用到线程隔离呢?

首先想到的就是SimpleDateFormat这个工具类,它不是线程安全的,可以通过ThreadLocal在每个线程中放一份,保证线程安全。

还有比如说用户登录的session,或者token数据,只数据当前会话线程,也可以通过ThreadLocal存储。

再比如在某些场景下,上下文数据在不同方法之间调用,传递起来非常麻烦,可以通过ThreadLocal存放,只需要在需要用到的地方获取就可以。

除了这些场景,在某些框架源码中也会使用到,比如Spring中的事务也主要是通过ThreadLocal和面向切面编程AOP实现的,感兴趣的同学可以查看源码了解。

避免踩坑

内存泄漏

ThreadLocalMap中的Entry的Key是一个弱引用,因此如果在使用后不调用remove方法清除掉会导致对应的value内存泄漏。所以在使用完以后一定要记得调用remove方法清除数据。


好的,今天的内容就到这里,我们下期再见。

关注我的公众号【黑子的学习笔记】干货内容第一时间送达。

并发编程之:ThreadLocal的更多相关文章

  1. 并发编程之ThreadLocal

    并发编程之ThreadLocal 前言 当多线程访问共享可变数据时,涉及到线程间同步的问题,并不是所有时候,都要用到共享数据,所以就需要线程封闭出场了. 数据都被封闭在各自的线程之中,就不需要同步,这 ...

  2. 并发编程之ThreadLocal、Volatile、synchronized、Atomic关键字扫盲

    前言 对于ThreadLocal.Volatile.synchronized.Atomic这四个关键字,我想一提及到大家肯定都想到的是解决在多线程并发环境下资源的共享问题,但是要细说每一个的特点.区别 ...

  3. 并发编程之ThreadLocal源码分析

    当访问共享的可变数据时,通常需要使用同步.一种避免同步的方式就是不共享数据,仅在单线程内部访问数据,就不需要同步.该技术称之为线程封闭. 当数据封装到线程内部,即使该数据不是线程安全的,也会实现自动线 ...

  4. Java并发编程之ThreadLocal解析

    本文讨论的是JDK 1.8中的ThreadLocal ThreadLocal概念 ThreadLocal多线程间并发访问变量的解决方案,为每个线程提供变量的副本,用空间换时间. ThreadLocal ...

  5. Java并发编程之ThreadLocal类

    ThreadLocal类可以理解为ThreadLocalVariable(线程局部变量),提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回当 ...

  6. Java并发编程之ThreadLocal源码分析

    ## 1 一句话概括ThreadLocal<font face="微软雅黑" size=4>  什么是ThreadLocal?顾名思义:线程本地变量,它为每个使用该对象 ...

  7. 并发编程之 ThreadLocal 源码剖析

    前言 首先看看 JDK 文档的描述: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局 ...

  8. 并发编程之 ThreadLocal

    前言 了解过 SimpleDateFormat 时间工具类的朋友都知道,该工具类非常好用,可以利用该类可以将日期转换成文本,或者将文本转换成日期,时间戳同样也可以. 以下代码,我们采用通用的 Simp ...

  9. 并发编程之 Exchanger 源码分析

    前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...

随机推荐

  1. 架构之:REST和HATEOAS

    目录 简介 HATEOAS简介 HATEOAS的格式 HATEOAS的Spring支持 总结 简介 我们知道REST是一种架构方式,它只是指定了六种需要遵循的基本原则,但是它指定的原则都比较宽泛,我们 ...

  2. CH1809 匹配统计 题解

    看了好久才懂,我好菜啊-- 题意:给两个字符串 \(a\) 与 \(b\),对于 \(q\) 次询问,每次询问给出一个 \(x\),求存在多少个位置使得 \(a\) 从该位置开始的后缀子串与 \(b\ ...

  3. GetOverlappedResult 函数

    BOOL GetOverlappedResult( HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWORD lpNumberOfBytesTransferre ...

  4. HashSet 的实现原理

    HashSet 概述 对于 HashSet 而言,它是基于 HashMap 实现的,底层采用 HashMap 来保存元素,所以如果对 HashMap 比较熟悉了,那么学习 HashSet 也是很轻松的 ...

  5. frameset框架在.net网站中的小实现。

    一般我们生成网页,为减少代码的开发量,通常将不变的网页部分进行重用.通用为三种方法: 1.frameset框架 2.用户自定义控件 3.母版页(消耗资源大,不追叙) 通常1,2两种方法常用. 1.fr ...

  6. 剖析虚幻渲染体系(08)- Shader体系

    目录 8.1 本篇概述 8.2 Shader基础 8.2.1 FShader 8.2.2 Shader Parameter 8.2.3 Uniform Buffer 8.2.4 Vertex Fact ...

  7. SQL语句(五)子查询

    目录 一.子查询含义 二.子查询分类 按子查询的位置分 按结果集的行列数不同分 三.WHERE后面的子查询 1. 标量子查询 2. 列子查询(多行子查询) 3. 行子查询(结果为一行多列或多行多列) ...

  8. 全网最硬核Handler面试题深度解析

    1.简述Handler的实现原理 Android 应用是通过消息驱动运行的,在 Android 中一切皆消息,包括触摸事件,视图的绘制.显示和刷新等等都是消息.Handler 是消息机制的上层接口,平 ...

  9. 尝新体验ASP.NET Core 6预览版本中发布的最小Web API(minimal APIS)新特性

    本文首发于<尝新体验ASP.NET Core 6预览版本中发布的最小Web API(minimal APIS)新特性> 概述 .NET开发者们大家好,我是Rector. 几天前(美国时间2 ...

  10. 《Python Cookbook v3.0.0》Chapter2 字符串、文本

    感谢: https://github.com/yidao620c/python3-cookbook 如有侵权,请联系我整改. 本文章节会严格按照原书(以便和原书对照,章节标题可能会略有修改),内容会有 ...