面试中的 ThreadLocal 原理和使用场景
相信大家不管是在网上做题还是在面试中都经常被问过 ThreadLocal 的原理和用法,虽然一直知道这个东西的存在但是一直没有好好的研究一下原理,没有自己的知识体系。今天花点时间好好学习了一下,分享给有需要的朋友。
ThreadLocal
是什么
ThreadLocal 是 JDK java.lang
包中的一个用来实现相同线程数据共享不同的线程数据隔离的一个工具。 我们来看下 JDK 源码中是如何解释的:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
大致的意思是
ThreadLocal 这个类提供线程局部变量,这些变量与其他正常的变量的不同之处在于,每一个访问该变量的线程在其内部都有一个独立的初始化的变量副本;ThreadLocal 实例变量通常采用
private static
在类中修饰。只要 ThreadLocal 的变量能被访问,并且线程存活,那每个线程都会持有 ThreadLocal 变量的副本。当一个线程结束时,它所持有的所有 ThreadLocal 相对的实例副本都可被回收。
一句话说就是 ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用(相同线程数据共享),也就是变量在线程间隔离(不同的线程数据隔离)而在方法或类间共享的场景。
ThreadLocal
使用
我们先通过两个例子来看一下 ThreadLocal
的使用
例子 1 普通变量
import java.util.concurrent.CountDownLatch;
public class MyStringDemo {
private String string;
private String getString() {
return string;
}
private void setString(String string) {
this.string = string;
}
public static void main(String[] args) {
int threads = 9;
MyStringDemo demo = new MyStringDemo();
CountDownLatch countDownLatch = new CountDownLatch(threads);
for (int i = 0; i < threads; i++) {
Thread thread = new Thread(() -> {
demo.setString(Thread.currentThread().getName());
System.out.println(demo.getString());
countDownLatch.countDown();
}, "thread - " + i);
thread.start();
}
}
}
程序的运行的随机结果如下:
thread - 1
thread - 2
thread - 1
thread - 3
thread - 4
thread - 5
thread - 6
thread - 7
thread - 8
Process finished with exit code 0
从结果我们可以看出多个线程在访问同一个变量的时候出现的异常,线程间的数据没有隔离。下面我们来看下采用 ThreadLocal
变量的方式来解决这个问题的例子。
例子 2 ThreadLocal
变量
import java.util.concurrent.CountDownLatch;
public class MyThreadLocalStringDemo {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
private String getString() {
return threadLocal.get();
}
private void setString(String string) {
threadLocal.set(string);
}
public static void main(String[] args) {
int threads = 9;
MyThreadLocalStringDemo demo = new MyThreadLocalStringDemo();
CountDownLatch countDownLatch = new CountDownLatch(threads);
for (int i = 0; i < threads; i++) {
Thread thread = new Thread(() -> {
demo.setString(Thread.currentThread().getName());
System.out.println(demo.getString());
countDownLatch.countDown();
}, "thread - " + i);
thread.start();
}
}
}
程序运行结果
thread - 0
thread - 1
thread - 2
thread - 3
thread - 4
thread - 5
thread - 6
thread - 7
thread - 8
Process finished with exit code 0
从结果来看,这次我们很好的解决了多线程之间数据隔离的问题,十分方便。
这里可能有的朋友会觉得在例子 1 中我们完全可以通过加锁来实现这个功能。是的没错,加锁确实可以解决这个问题,但是在这里我们强调的是线程数据隔离的问题,并不是多线程共享数据的问题。假如我们这里除了getString()
之外还有很多其他方法也要用到这个 String,这个时候各个方法之间就没有显式的数据传递过程了,都可以直接中 ThreadLocal
变量中获取,这才是 ThreadLocal
的核心,相同线程数据共享不同的线程数据隔离。
由于ThreadLocal
是支持泛型的,这里采用的是存放一个 String
来演示,其实可以存放任何类型,效果都是一样的。
ThreadLocal
源码分析
在分析源码前我们明白一个事那就是对象实例与 ThreadLocal
变量的映射关系是由线程 Thread
来维护的,对象实例与 ThreadLocal
变量的映射关系是由线程 Thread
来维护的,对象实例与 ThreadLocal
变量的映射关系是由线程 Thread
来维护的。重要的事情说三遍。换句话说就是对象实例与 ThreadLocal
变量的映射关系是存放的一个 Map
里面(这个 Map
是个抽象的 Map
并不是 java.util
中的 Map
),而这个 Map
是 Thread
类的一个字段!而真正存放映射关系的 Map
就是 ThreadLocalMap
。下面我们通过源码的中几个方法来看一下具体的实现。
//set 方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//获取线程中的ThreadLocalMap 字段!!
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//创建线程的变量
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
在 set
方法中首先获取当前线程,然后通过 getMap
获取到当前线程的 ThreadLocalMap
类型的变量 threadLocals
,如果存在则直接赋值,如果不存在则给该线程创建 ThreadLocalMap
变量并赋值。赋值的时候这里的 this
就是调用变量的对象实例本身。
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();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
get
方法也比较简单,同样也是先获取当前线程的 ThreadLocalMap
变量,如果存在则返回值,不存在则创建并返回初始值。
ThreadLocalMap
源码分析
ThreadLocal
的底层实现都是通过 ThreadLocalMap
来实现的,我们先看下 ThreadLocalMap
的定义,然后再看下相应的 set
和 get
方法。
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<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
}
ThreadLocalMap
中使用 Entry[]
数组来存放对象实例与变量的关系,并且实例对象作为 key,变量作为 value 实现对应关系。并且这里的 key 采用的是对实例对象的弱引用,(因为我们这里的 key 是对象实例,每个对象实例有自己的生命周期,这里采用弱引用就可以在不影响对象实例生命周期的情况下对其引用)。
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//获取 hash 值,用于数组中的下标
int i = key.threadLocalHashCode & (len-1);
//如果数组该位置有对象则进入
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//k 相等则覆盖旧值
if (k == key) {
e.value = value;
return;
}
//此时说明此处 Entry 的 k 中的对象实例已经被回收了,需要替换掉这个位置的 key 和 value
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//创建 Entry 对象
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//获取 Entry
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
至此我们看完了 ThreadLocal
相关的 JDK 源码,我自己也有了更深入的了解,也希望能帮助到大家。
小结
在平时忙碌的工作中我们经常解决的是一个业务的需求,往往很少会涉及到底层的源码或者框架的具体实现代码。 其实这是很不好的,其实很多的东西的原理都是一样的,我们需要经常去看一下源码,了解一些底层的实现,不能总是停留在表层,代码看到多了,才能写出好的代码,并且还能学到很多东西。 随着我们知道的越来越多,我们会发现我们不知道的也越来越多。加油,共勉!
Java 极客技术公众号,是由一群热爱 Java 开发的技术人组建成立,专注分享原创、高质量的 Java 文章。如果您觉得我们的文章还不错,请帮忙赞赏、在看、转发支持,鼓励我们分享出更好的文章。
关注公众号,大家可以在公众号后台回复“博客园”,免费获得作者 Java 知识体系/面试必看资料。
面试中的 ThreadLocal 原理和使用场景的更多相关文章
- ThreadLocal 原理和使用场景分析
ThreadLocal 不知道大家有没有用过,但至少听说过,今天主要记录一下 ThreadLocal 的原理和使用场景. 使用场景 直接定位到 ThreadLocal 的源码,可以看到源码注释中有很清 ...
- 聊聊ThreadLocal原理以及使用场景-JAVA 8源码
相信很多人知道ThreadLocal是针对每个线程的,但是其中的原理相信大家不是很清楚,那咱们就一块看一下源码. 首先,我们先看看它的set方法.非常简单,从当前Thread中获取map.那么这个ge ...
- 简单理解ThreadLocal原理和适用场景
https://blog.csdn.net/qq_36632687/article/details/79551828?utm_source=blogkpcl2 参考文章: 正确理解ThreadLoca ...
- 深入解析ThreadLocal 详解、实现原理、使用场景方法以及内存泄漏防范 多线程中篇(十七)
简介 从名称看,ThreadLocal 也就是thread和local的组合,也就是一个thread有一个local的变量副本 ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个 ...
- 在spring+beranate中多数据源中使用 ThreadLocal ,总结的原理 --费元星
设计模式 首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问 ...
- C++算法原理与实践(面试中的算法和准备过程)
第0部分 简介 1. 举个例子:面试的时候,可能会出一道算法考试题,比如写一个 strstr 函数——字符串匹配. 可能会想到用KMP算法来解题,但是该算法很复杂,不适宜在面试中使用. 1.1 C++ ...
- 面试中关于Java你所需知道的的一切
本篇文章会对面试中常遇到的Java技术点进行全面深入的总结,帮助我们在面试中更加得心应手,不参加面试的同学也能够借此机会梳理一下自己的知识体系,进行查漏补缺. 1. Java中的原始数据类型都有哪些, ...
- ThreadLocal原理及其实际应用
前言 java猿在面试中,经常会被问到1个问题: java实现同步有哪几种方式? 大家一般都会回答使用synchronized, 那么还有其他方式吗? 答案是肯定的, 另外一种方式也就是本文要说的Th ...
- 面试中关于Java中涉及到知识点(转)
本篇文章会对面试中常遇到的Java技术点进行全面深入的总结,帮助我们在面试中更加得心应手,不参加面试的同学也能够借此机会梳理一下自己的知识体系,进行查漏补缺. 1. Java中的原始数据类型都有哪些, ...
随机推荐
- Python连载14-random模块&函数式编程
一.random模块 1.函数:random() (1)用法:获取0~1之间的随即小数 (2)格式:random.random() (3)返回值:随机0~1之间的小数 2.函数:choice() ( ...
- Fabric1.4源码解析:Peer节点背书提案过程
以前从来没有写过博客,从这段时间开始才开始写一些自己的博客,之前总觉得写一篇博客要耗费大量的时间,而且写的还是自己已经学会的,觉得没什么必要.但是当开始用博客记录下来的时候,才发现有些学会的地方只是自 ...
- 视频直播:Windows中各类画面源的截取和合成方法总结
当今,视频直播技术和实时音视频技术已经是很多行业必备,典型的应用场景有教育直播.远程视频会议.互联网娱乐等.在移动端发起直播,其画面源的种类是十分有限的,无非是取摄像头.截屏等.PC端由于其系统资源充 ...
- Centos7下安装Mysql8.0
突然发现mysql都有8.0了,且性能提升比较明显,就自己装来玩玩. centos的yum源中默认是没有mysql的,所以我们需要先去官网下载mysql的repo源并安装: 官网:http://dev ...
- 将GMap封装为Activex供QT使用(工具:VS2017,QT5.12) 更新部署到其它电脑
由于一开始定的开发平台在QT下,到后面要加入地图控件.qt里本身带有地图qmap(在qt的官方案例中可以找到,用qml做的),但只有固定的几个地图源,要做google或者bing地图,时间和人力不允许 ...
- Web自动化测试 二 ----- HTML
HTML 一.结构 html> 与 </html> 之间的文本描述网页 <body> 与 </body> 之间的文本是可见的页面内容 <h1> 与 ...
- 为什么wepy的数据无法渲染到实例里面去
为啥明明WEPY是将数据渲染到视图还会出这个问题呢? 因为只是类似于 Vue 框架,所以不能完全实现数据渲染,尤其当异步操作的时候. (1)WePy 普通数据绑定. WePY使用脏数据检查对setDa ...
- idea创建springcloud主工程和springboot子项目
创建主工程,选择file-new-project,选择maven,直接next 填写GroupId包名,ArtifactId项目名,next-finish 创建子项目springboot,项目右击-n ...
- Maven项目读取resources下文件的路径
要取编译后的路径,而不是你看到的src/main/resources的路径.如下: URL url = 类名.class.getClassLoader().getResource("conf ...
- 源码阅读 - java.util.concurrent (二)CAS
背景 在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁 锁机制存在以下问题: (1)在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问题. ...