社区编辑申请
注册/登录
ThreadLocal的使用及实现原理
开发 前端
ThreadLocal可以用来把实例变量共享成全局变量,让程序中所有的方法都可以访问到该变量。

前言

ThreadLocal直译是本地线程,但实际上它的译名是线程局部变量(ThreadLocalVariable)。ThreadLocal诞生的目的是隔离不同线程所使用的变量,官方对它的解释是:

提供了线程局部变量,是独立于变量的初始化副本”,也就是说它可以实现将某一个变量隔离在某个线程内,其它的线程无法访问和使用这个变量。

我们先来做一个测试,先不使用ThreadLocal,创建三个线程:

public class ThreadLocalTest {
public static int num = 0;
public static int numAdd() {
return num++;
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread(new MyRunnable());
t1.start();
t2.start();
t3.start();
}
public static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "-" + ThreadLocalTest.numAdd());
}
}

}
}

执行后发现控制台输出的是:

可以发现线程执行了numAdd()方法,从0-8跑了九次,num从0加到8,也就是说线程之间共享了静态变量,从而导致线程的不安全问题。

然后我们再使用ThreadLocal来进行测试。

public class ThreadLocalTest {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static int numAdd() {
threadLocal.set(threadLocal.get()+1);
return threadLocal.get();
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread(new MyRunnable());
t1.start();
t2.start();
t3.start();
}
public static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "-" + numAdd());
}
}

}
}

这里的numAdd方法使用了ThreadLocal的get()方法,这个方法调用了initialValue()方法并设置了返回值为0,通过调用这个方法+1,达到了num++的效果,这时候再看输出的结果。

可以看到,三个不同的线程间相互隔离,变量的取值互不相干,也就是说ThreadLocal使用了不相干的变量,或者说ThreadLocal为每一个线程准备了一个变量副本,那么它是如何实现的呢,我们点进ThreadLocal的源码看看。

这就是ThreadLocal的构成了,主要操作是get()和set()方法:

get() : 返回当前ThreadLocal的值

 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();
}

set() : 将当前线程对象的值存入ThreadLocalMap中

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

首先创建了当前Thread的对象,然后存入ThreadLocalMap中,对map进行判断,不为空就将this(当前Threadlocal对象)存入作为key,并获取对应的值,最后是调用了一个setInitialValue()方法去获得初始化的值。

ThreadLocalMap

介绍上面两个方法主要是是为了引出ThreadLocal的实现原理,即ThreadLocalMap的创建和使用。

官方注释中解释道,ThreadLocalMap是一个定制的哈希映射,只适用于维护ThreadLocal的值。在ThreadLocal类之外没有导出操作。类是包私有的,以允许在类线程中声明字段。为了帮助处理非常大且长期使用的用法,哈希表条目对键使用弱引用。

但是,由于不使用引用队列,只有当表开始耗尽空间时,才开始删除陈旧的条目。

点开ThreadLocalMap,可以看到一开始ThreadLocalMap定义了一个用于存储数据的Entry 类。

 static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

这个Entry类继承了弱引用类,众所周知Java有四种引用类型,其中弱引用就是每次JVM进行垃圾回收时,都会回收该对象,保证了ThreadLocal每次拷贝当前线程的值的时候所占的空间能被重新使用。

由get()方法可以得知,ThreadLocalMap的键(key)是ThreadLocal类的实例对象,value为用户的值。

那么ThreadLocalMap的引用是在哪里呢,在上面的set()方法里,调用了getMap()和createMap()方法。

ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

可以看到这边调用了一个叫threadLocals的属性,点击这个属性发现跳到了Thread类中。

  /* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

所以这个属性便是ThreadLocalMap的引用了,那么ThreadLocal的实现原理也就很清晰了:

  1. 定义了一个ThreadLocalMap内部类,使用的是Map的键值对方式来存取数据,key是ThreadLocal类的实例对象,value为传值。
  2. 创建新的ThreadLocal对象,调用set()或get()方法时,也就是调用了ThreadLocalMap来进行操作。
  3. 使用ThreadLocal时,线程所使用的变量是独享的(私有的变量副本),其他线程无法访问,在使用过后(线程结束),这些变量会被GC回收。

使用ThreadLocal的原因

ThreadLocal可以用来把实例变量共享成全局变量,让程序中所有的方法都可以访问到该变量。

由于存到ThreadLocal的变量都是当前线程本身,其他线程无法访问,存到ThreadLocal中只是为了方便在程序中同一个线程之间传递这个变量(和解决线程安全没有关系)。

责任编辑:姜华 来源: 今日头条
相关推荐

2022-06-12 06:48:34

2022-06-15 11:02:40

网络安全运营

2022-05-23 07:48:10

zabbix监控CentOS7

2021-05-13 17:02:38

MDC脚手架日志

2022-05-11 09:02:27

Python数据库Excel

2022-06-28 08:40:16

LokiPromtail日志报警

2022-06-01 17:47:24

运维监控系统

2022-06-17 09:08:27

代码Python内置库

2022-05-11 15:08:52

驱动开发系统移植

2022-06-27 17:46:53

PythonFlask

2022-06-20 13:34:46

漏洞网络攻击

2022-06-06 14:35:59

KubevirtKubernetes虚拟机

2022-06-09 16:07:55

SAP双碳碳排放

2022-06-27 15:25:08

架构模型治理

2022-05-12 10:53:42

keepalivevrrp协议

2022-06-15 16:16:21

分布式数据库鸿蒙

2022-05-24 15:06:57

AbilityeTS FA鸿蒙

2022-06-07 14:31:09

K8S网络模型容器网络

2022-05-17 09:14:50

聚类算法python

2022-06-02 10:54:01

同话题下的热门内容

哪个版本的JVM最快?无代码软件发展简史及未来趋势携程基于 GraphQL 的前端 BFF 服务开发实践为什么会存在 1px 问题?怎么解决?一文搞定常考Vue-Router知识点EcmaScript 2022 正式发布,有哪些新特性?一文详解|增长那些事儿远程医疗:优势、前景和现有IT解决方案

编辑推荐

太厉害了,终于有人能把TCP/IP协议讲的明明白白了!牛人5次面试腾讯不成功的经验HBase原理–所有Region切分的细节都在这里了Javascript如何监听页面刷新和关闭事件如何搭建一个HTTPS服务端
我收藏的内容
点赞
收藏

51CTO技术栈公众号