结合源码谈谈ThreadLocal!
网上有很多关于ThreadLocal的介绍,有的介绍比较简单,也有的介绍很复杂,比较难懂,今天,自己结合它的源码,也做个简易梳理,记录如下!
ThreadLocal的作用
在多请求并发访问过程中,我们往往需要将一个指定变量隔离起来,达到只对当前线程可用,其他线程不可用的效果,因此,我们就会使用到ThreadLocal来实现。
实现原理其实就是在每个线程中维护了一个Map结构(ThreadLocalMap,它是ThreadLocal中的静态内部类),ThreadLocal对象为Key,需要隔离的值为Value。为了达到线程全局可用,我们往往将ThreadLocal声明为全局静态变量。
Thread中的ThreadLocalMap对象

那么ThreadLocal具体如何做到线程隔离的?我们下面做具体分析!
ThreadLocal
我们暂时先不分析ThreadLocalMap,单独来看ThreadLocal的几个方法源码介绍!
1.对象初始化
ThreadLocal初始化比较简单!
public static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
我们往往在初始化时会给他指定一个默认值,不指定的话,默认值为null,这里有两种指定方式:
第一种:直接复写ThreadLocal中的initialValue方法
第二种:利用函数式编程,创建SuppliedThreadLocal对象,由get方法直接返回初始值
public static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "Test";
}
};
public static final ThreadLocal<String> THREAD_LOCAL = ThreadLocal.withInitial(()->"Test");
SuppliedThreadLocal对象是对ThreadLocal的一个特定实现,通过构造函数传入Supplier,再由实现的initialValue方法返回supplier.get()的结果,其他也没什么可多介绍的。
2.获取变量
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();
}
从get方法我们可以看到,ThreadLocal是从当前线程中获取到了ThreadLocalMap对象,然后取出其中的Entry.Value值,如果对象不存在就返回初始值,初始化方法initialValue会在这里调用一次,其他操作不再调用。
3.设置变量
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法与get方法一样,会通过当前线程取出ThreadLocalMap对象,然后将当前ThreadLocal对象作为Key,存储Value值,ThreadLocalMap不存在时,会创建新的Map。
4.移除变量
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
移除变量同样是根据currentThread来找到的Map,然后对当前ThreadLocal做remove操作。
ThreadLocalMap
通过ThreadLocal的操作介绍我们可以看到,ThreadLocal的操作都是基于ThreadLocalMap来实现的,所以,ThreaLocalMap才是我们对ThreadLocal变量实现线程隔离的重点。
1.Entry
ThreadLocalMap中存储数据关系的是Entry,它的Key是ThreadLocal对象,采用弱引用,Value是一个强引用对象Object。当Entry.get()获取的ThreadLocal为Null时,GC回收将直接清除该对象,但Value对象,需要我们手动清除,所以,我们需要在每个ThreadLocal调用结束时,执行remove方法。关于弱引用与强引用的关系以及他们的对象回收机制,这里不做过多介绍,有兴趣的同学可以自行学习!
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
2.初始化
ThreadLocalMap的操作是基于Entry[]数组table完成的,数组初始化大小为16。table是一个2的N次方的数组,ThreadLocal通过AtomicInteger类型的nextHashCode,每次偏移HASH_INCREMENT=0x61c88647的大小来实现数据在数组上的平均分布。
关于Entry[]中如何解决碰撞冲突问题,可以参考:ThreadLocal 和神奇的数字 0x61c88647
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
3.获取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);
}
查询table中的Entry值时,采用神奇的0x61c88647,ThreadLocal对象作为Key与Entry的Key相同时,返回此Entry,否则,会从i开始迭代查找Entry。
4.设置Entry
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
set方法中兼容新增与修改操作,如果找到同一个ThreadLocal对应的Entry时,则直接重新赋值Value,否则新建Entry执行table[i]。
5.移除Entry
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
remove操作同样通过循环table,找到Key相同的ThreadLocal对象,然后通过指定弱引用的Key值为Null移除,并将table[i].value也置为Null。
持续更新中...
结合源码谈谈ThreadLocal!的更多相关文章
- 深入源码理解ThreadLocal和ThreadLocalMap
一.ThreadLoacl的理解: 官方的讲: ThreadLocal是一个本地线程副本变量工具类,主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰 通俗的讲: Thre ...
- JDK源码之ThreadLocal
1.定义 ThreadLocal是线程变量,就是说每一个线程都有对应的该变量副本,线程修改该变量时,线程与线程之间的变量是相互隔离的,互相并看不见.这个结构附带在线程上,一个线程可以根据ThreadL ...
- 从源码理解 ThreadLocal
为每个线程保存各自的拷贝,可以通过在Thread类中定义一个成员变量来保存每个线程值,这样也是线程安全的. 通过定义一个成员变量 sn 来实现,这里并没有使用ThreadLocal类来实现: publ ...
- Netty源码分析-- ThreadLocal分析(九)
为了更好地探讨Netty的内存模型,后面会用到,这里我还是决定跟大家一起看下ThreadLocal和FastThreadLocal的源码,有的时候我们在看源码的时候会一层层的遇到很多之前没有看过的内容 ...
- Java读源码之ThreadLocal
前言 JDK版本: 1.8 之前在看Thread源码时候看到这么一个属性 ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal实现的是 ...
- 从 源码 谈谈 redux compose
compose,英文意思 组成,构成. 它的作用也是通过一系列的骚操作,实现任意的.多种的.不同的功能模块的组合,用来加强组件. 看看源码 https://github.com/reactjs/red ...
- ThreadLocal 源码剖析
ThreadLocal是Java语言提供的用于支持线程局部变量的类.所谓的线程局部变量,就是仅仅只能被本线程访问,不能在线程之间进行共享访问的变量(每个线程一个拷贝).在各个Java web的各种框架 ...
- Java多线程9:ThreadLocal源码剖析
ThreadLocal源码剖析 ThreadLocal其实比较简单,因为类里就三个public方法:set(T value).get().remove().先剖析源码清楚地知道ThreadLocal是 ...
- 多线程爬坑之路-ThreadLocal源码及原理的深入分析
ThreadLocal<T>类:以空间换时间提供一种多线程更快捷访问变量的方式.这种方式不存在竞争,所以也不存在并发的安全性问题. This class provides thread-l ...
随机推荐
- 剑指offer数组中重复的数字
package 数组; /*在一个长度为n的数组里的所有数字都在0到n-1的范围内. 数组中某些数字是重复的,但不知道有几个数字是重复的. 也不知道每个数字重复几次.请找出数组中任意一个重复的数字. ...
- Magento1.9 add attribute to catalog product & assign to all attribute set general group
$installer = $this; $attributes = array( 'region' => array( 'type' => 'int', 'input' => 'se ...
- pypcap rpm制作
1.下载地址 https://pypi.org/project/pypcap/#history 2.下载后,解压并制作rpm tar -xvf pypcap-1.2.3.tar.gz python s ...
- 四维dp,传纸条,方格取数
四维dp例题 四维dp便是维护4个状态的dp方式 拿题来说吧. 1. 洛谷P1004 方格取数 #include<iostream> #include<cstdio> usin ...
- Java面试题(网络篇)
网络 79.http 响应码 301 和 302 代表的是什么?有什么区别? 301,302 都是HTTP状态的编码,都代表着某个URL发生了转移. 区别: 301 redirect: 301 代表永 ...
- 一键洞察全量SQL ,远离性能异常
随着云数据库业务规模不断扩大,用户对安全审计.性能智能分析等高级特性的要求越来越高.如何安全审计SQL?如何快速定位性能异常?如何保护数据库安全?是每个云厂商不得不思考的问题. 华为云DAS服务提供的 ...
- (Android图片内存优化)Picasso加载图片 教程。。详细版
Picasso 是 Android 上一个强大的图片下载和缓存库. 示例代码: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Picasso.with( ...
- Android 设备指纹
Android唯一识别号(设备指纹)的生成及原理 https://blog.csdn.net/xiechengfa/article/details/70049409?utm_source=itdada ...
- python小白入门基础(三:整型)
# Number(int float str complex) #int 整型(正整数 0 负整数)intvar_1 = 100print(intvar_1)invar_2 = 0 print(inv ...
- A+B in Hogwarts (20)(模拟)
时间限制 1000 ms 内存限制 65536 KB 代码长度限制 100 KB 判断程序 Standard (来自 小小) 题目描述 If you are a fan of Harry Potter ...