线程局部变量ThreadLocal实现原理
ThreadLocal,即线程局部变量,用来为每一个使用它的线程维护一个独立的变量副本。这种变量只在线程的生命周期内有效。并且与锁机制那种以时间换取空间的做法不同,ThreadLocal没有任何锁机制,它以空间换取时间的方式保证变量的线程安全。
本篇从源码方面分析ThreadLocal的实现原理。
先看一下ThreadLocal类图结构

SuppliedThreadLocal主要是JDK1.8用来扩展对Lambda表达式的支持,有兴趣的自行百度。
ThreadLocalMap是ThreadLocal的静态内部类,也是实际保存变量的类。
Entry是ThreadLocalMap的静态内部类。ThreadLocalMap持有一个Entry数组,以ThreadLocal为key,变量为value,封装一个Entry。
下面以一张图简要说明Thread,ThreadLocal,ThreadLocalMap和Entry的关系。

说明一下上图:
- 一个Thread拥有一个ThreadLocalMap对象
- ThreadLocalMap拥有一个Entry数组
- 每个Entry都有k--v
- Entry的key就是某个具体的ThreadLocal对象
下面分析主要方法。
1、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);
}
这里可以看出:一个Thread只拥有一个ThreadLocalMap对象;具体存值调用的是ThreadLocalMap的set(),传入的参数key就是当前ThreadLocal对象。
再看看ThreadLocalMap的set()方法:
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-); // 1
for (Entry e = tab[i]; // 2
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); // 3
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold) // 4
rehash();
}
- 通过key的hashCode与数组容量 -1 取模,计算数组index
- 从当前index开始遍历,清除key为null的无效Entry
- 将K-V封装为Entry,并放入数组
- 判断是否需要进行Entry数组扩容。threshold的值为数组容量的2/3。
看看扩容的resize()方法:
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * ;
Entry[] newTab = new Entry[newLen];
int count = ;
for (int j = ; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - );
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
这里主要就是扩容为原先的2倍。然后遍历旧数组,根据新数组容量重新计算Entry在新数组中的位置。
2、get()
ThreadLocal的get()方法如下:
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的getEntry()方法如下:
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - ); // 1
Entry e = table[i];
if (e != null && e.get() == key) // 2
return e;
else
return getEntryAfterMiss(key, i, e); //3
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) { //4
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
- 计算index
- 当前index上的Entry不为空且key相同,直接返回
- 否则去相邻index寻找
- 循环查找,发现无效key就清除。找到就结束循环。
3、remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
处理方式和查找保存类似,删除对应Entry后都会去除key为null的无效元素。
注意
static class Entry extends WeakReference<ThreadLocal<?>> {}
ThreadLocal可能存在OOM问题。因为ThreadLocalMap是使用ThreadLocal的弱引用作为key的,发生GC时,key被回收,这样我们就无法访问key为null的value元素,如果value本身是较大的对象,那么线程一直不结束的话,value就一直无法得到回收。特别是在我们使用线程池时,线程是复用的,不会杀死线程,这样ThreadLocal弱引用被回收时,value不会被回收。
在使用ThreadLocal时,线程逻辑代码结束时,必须显示调用ThreadLocal.remove()方法。
线程局部变量ThreadLocal实现原理的更多相关文章
- 线程局部变量ThreadLocal的原理及使用范围_1
线程局部变量ThreadLocal的原理及使用范围 使用原理 每个Thread中都有一个ThreadLocalMap成员, 该成员是ThreadLocal的内部类ThreadLocalMap类型.每使 ...
- ThreadLocal工作原理
原文出处: imzoer 在这篇文章中,总结了一下面试过程中遇到的关于ThreadLocal的内容.总体上说,这样回答,面试算是过得去了.但是,这样的回答,明显仅仅是背会了答案,而没有去研究Threa ...
- java线程——线程局部变量
一,线程局部变量ThreadLocal的作用 用于实现线程内部的数据共享,既对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,在另一个线程访问的时候,访问的由是另一份数据. 每个线程调用 ...
- Java基础教程——线程局部变量
线程局部变量 ThreadLocal,线程局部变量,不提供锁,不做线程共享,而是为每个线程提供变量的独立副本. import java.util.concurrent.*; public class ...
- Java并发编程原理与实战二十五:ThreadLocal线程局部变量的使用和原理
1.什么是ThreadLocal ThreadLocal顾名思义是线程局部变量.这种变量和普通的变量不同,这种变量在每个线程中通过get和set方法访问, 每个线程有自己独立的变量副本.线程局部变量不 ...
- 【java】ThreadLocal线程变量的实现原理和使用场景
一.ThreadLocal线程变量的实现原理 1.ThreadLocal核心方法有这个几个 get().set(value).remove() 2.实现原理 ThreadLocal在每个线程都会创建一 ...
- python 线程中的局部变量ThreadLocal
一个线程使用自己的局部变量比使用全局变量好局部变量只有线程自己能看见,不会影响其他线程全局变量的修改必须加锁 ThreadLocal 线程局部变量 import threading # 创建全局Thr ...
- ThreadLocal线程局部变量的使用
ThreadLocal: 线程局部变量 一).ThreadLocal的引入 用途:是解决多线程间并发访问的方案,不是解决数据共享的方案. 特点:每个线程提供变量的独立副本,所有的线程使用同一个Thre ...
- 浅谈Flask 中的 线程局部变量 request 原理
2017-11-27 17:25:11 晚橙 阅读数 600更多 分类专栏: Flask python 多线程 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出 ...
随机推荐
- ASP:CheckBox获取前台的checked的属性
后台代码: for (int i = 0; i < this.GvCourses.Rows.Count; i++) { CheckBox chb = this.GvCourses.Rows[i] ...
- B/S实现大视频上传
在公司做B/S 开发与维护三年啦, 对B/S架构的了解也是只知大概,对于这种基础知识还是很有必要理一理哒.趁空去网上查阅了资料,顺便整理一份笔记供以后查询. 一. B/S的概念 B/S(Brower/ ...
- CodeForces 1198D 1199F Rectangle Painting 1
Time limit 1000 ms Memory limit 262144 kB 解题思路 一堆循环嵌套的那种dp,不好想.但是可以搜啊,很暴力的.记忆化一下就好. 我们定义搜索函数\(\text{ ...
- 大数据笔记(二十一)——NoSQL数据库之Redis
一.Redis内存数据库 一个key-value存储系统,支持存储的value包括string(字符串).list(链表).set(集合).zset(sorted set--有序集合)和hash(哈希 ...
- php简单随机实现发红包程序
前言: 使用PHP发红包,当我们输入红包数量和总金额后,PHP会根据这两个值进行随机分配每个金额,保证每个人都能领取到一个红包,每个红包金额不等,就是要求红包金额要有差异,所有红包金额总额应该等于总金 ...
- ORACLE Physical Standby 级联备库搭建
搭建oracle 级联DG 现有架构:physical standby 一主二备,在此基础上,在主库下新建备库standby3.级联备库cascade 数据库版本 11.2.0.4 db_name=p ...
- ASP 数据库分页
<%@LANGUAGE="VBSCRIPT" CODEPAGE="936"%> <% Response.buffer=false %> ...
- flex embed 使用
Flex 软件中经常需要使用一些外部的资源,如图片.声音.SWF或字体,虽然你也可以在软件运行的时候引入和载入,但是也可能经常需要直接将这些资源编译(Compile)到软件中,也就是直接嵌入资源(Em ...
- 2019 Nanchang Onsite
D.Interesting Series F(n)实际上是一个等比数列的和,将它从递推式转变为通项公式(a^n-1)/(a-1),这里只需要确定n就可以. 题目要求选取k大小的所有子集的答案求和,可以 ...
- Map类集合遍历