结合源码谈谈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 ...
随机推荐
- hdfs-default.xml
<?xml version="1.0"?> <?xml-stylesheet type="text/xsl" href="confi ...
- composer安装包的时候触发PHP fatal error,提示允许的内存耗光
在composer require之前加COMPOSER_MEMORY_LIMIT=-1
- Today At AntDock 2020年8月21日
前段时间,我在 Mac 上使用 CLion 进行 C++ 编译,但发现 #include<bits/stdc++.h> 居然出了问题,无法使用,Xcode 也不行.我试图查找资料,但一般都 ...
- 牛客网PAT练兵场-德才论
题解:用sort排序即可 题目地址:https://www.nowcoder.com/questionTerminal/97b6a49a85944650b2e3d0660b91c324 /** * C ...
- 学生成绩管理系统(SSM+MySQL+JSP)
开发工具:Eclipse前端技术:基础:html+css+JavaScript框架:JQuery+H-ui后端技术:Spring+SpringMVC+mybatis模板引擎:JSP数据库:mysql ...
- Linux离线安装mysql 5.6详细步骤
一.安装MySQL 1.下载安装包 mysql-5.6.40-linux-glibc2.12-x86_64.tar.gz 2.卸载系统自带的Mariadb rpm -qa|grep mariadb / ...
- win10下MinGW的安装与配置(详细步骤)
一.安装mingw软件 1.进入官网 www.mingw.org 2.点击下载downloads 3.点击下载图标 4.点击install z 5.先选择安装的地址,再点击continue 6.等待相 ...
- 冒泡排序的优化方案BubbleSort
<?php /** * 冒泡排序 * * ------------------------------------------------------------- * 思路分析:就是像冒泡一样 ...
- 【Android】AndroidStudio关于EventBus报错解决方法its super classes have no public methods with the @Subscribe
作者:程序员小冰,GitHub主页:https://github.com/QQ986945193 新浪微博:http://weibo.com/mcxiaobing 首先说明,以前我用eventBus的 ...
- Cross-Site Scripting: Reflected
首先贴解决办法吧,解决了我项目中的问题,不一定适用所有情况. //For Cross-Site Scripting: Reflected public static String filter(Str ...