ThreadLocal源码阅读笔记
功能描述
ThreadLocal解决了访问共享变量的阻塞问题,并且不需要像CAS操作一样牺牲CPU资源,它为每一个线程维护了一个变量副本,每个线程在访问ThrealLocal里面的变量时实际上访问的是自己线程内的变量副本,并且这个线程内的变量副本与其他线程的变量副本相互隔离,互不影响。也就是说,ThreadLocal包裹的变量是线程级变量。

源码解刨
ThreadLocal通过一个内部类ThreadLocalMap进行数据的保存,并将自己本身作为key,从get方法入手。
public T get() {
// 取得当前线程
Thread t = Thread.currentThread();
// 取得当前线程内的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 使用threadlocal作为key在ThreadLocalMap内取得Entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 取得threadlocal包裹的值在该线程内的副本
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
在get方法内发现,Threadlocal首先获取了当前线程,然后使用当前线程作为key取得ThreadLocalMap对象,那么这个ThreadLocalMap对象仅对当前线程可见,ThreadLocalMap内包含的内容也仅对当前线程可见,查看getMap方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
此时发现ThreadLocalMap实际上保存在Thread类的threadLocals变量中,查看Thread类代码,其内部保存了ThreadLocalMap类变量threadLocals,即ThreadLocalMap定义在ThreadLocal类中,却实际保存在Thread类中。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

观察ThreadLocalMap类的定义
static class ThreadLocalMap {
// 静态内部类,保存键值对,并且使用了弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
使用弱引用的原因主要是为了帮助jvm进行垃圾回收(可以参考WeakHashMap)

通过内存引用关系图可以发现,对ThreadLocal的引用有两处,即自定义的threadlocal变量和Entry内的key,如果Entry内的key持有ThreadLocal的强引用,那么即使将自定义的threadlocal变量设置为空,由于key的存在,也无法对threadlocal所占用的内存进行回收,就会造成内存泄漏问题。
Entry内的key持有ThreadLocal的弱引用,当自定义的threadlocal变量设置为null时,只有key引用到了java堆内的ThreadLocal,因为弱引用的特性,如果没有其他强引用连接,则可以被回收,因此不会造成内存泄漏问题。
如果通过将ThreadLocal设置为null来帮助GC时发现,threadlocal变量可以被回收掉,但是如果之前未将value清空的话,value会一直持有引用,会造成内存泄漏问题。因此当某个线程内的threadlocal使用完了,一定要先调用remove方法清空value,在设置threadlocal为null。
搞清楚了弱引用的作用后,继续看ThreadLocalMap类的getEntry方法
private Entry getEntry(ThreadLocal<?> key) {
// 通过threadlocal类的hashcode取得Entry在table中的下标
int i = key.threadLocalHashCode & (table.length - 1);
// 在table中取得Entry对象
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
通过以上代码发现,ThreadLocalMap实际的存储结构是Entry[] table,而table的结构为hash表,为了验证这一观点,继续查看ThreadLocalMap类的set方法
private void set(ThreadLocal<?> key, Object value) {
// 取得Entry表
Entry[] tab = table;
// 取得表格长度
int len = tab.length;
// 通过threadlocal类的hashcode取得Entry在table中的下标位置
int i = key.threadLocalHashCode & (len-1);
// 如果Entry[i]不为空,从下标i开始遍历表格
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// threallocal与entry中的key相同,直接替换值
if (k == key) {
e.value = value;
return;
}
// key为null,设置key,value并修改hashcode
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// Entry[i]为空,直接设置key和value
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
看完了set方法,此时大致对table的结构有了一定的掌握。

设置值时通过threadlocal的hash码与table的长度来获取要存储的下标位置,获取value时也是同样的方式。
ThreadLocal源码阅读笔记的更多相关文章
- CI框架源码阅读笔记5 基准测试 BenchMark.php
上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功 ...
- CI框架源码阅读笔记4 引导文件CodeIgniter.php
到了这里,终于进入CI框架的核心了.既然是“引导”文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.c ...
- CI框架源码阅读笔记3 全局函数Common.php
从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap ...
- CI框架源码阅读笔记2 一切的入口 index.php
上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里再次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中, ...
- 源码阅读笔记 - 1 MSVC2015中的std::sort
大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格 ...
- Three.js源码阅读笔记-5
Core::Ray 该类用来表示空间中的“射线”,主要用来进行碰撞检测. THREE.Ray = function ( origin, direction ) { this.origin = ( or ...
- PHP源码阅读笔记一(explode和implode函数分析)
PHP源码阅读笔记一一.explode和implode函数array explode ( string separator, string string [, int limit] )此函数返回由字符 ...
- AQS源码阅读笔记(一)
AQS源码阅读笔记 先看下这个类张非常重要的一个静态内部类Node.如下: static final class Node { //表示当前节点以共享模式等待锁 static final Node S ...
- libevent源码阅读笔记(一):libevent对epoll的封装
title: libevent源码阅读笔记(一):libevent对epoll的封装 最近开始阅读网络库libevent的源码,阅读源码之前,大致看了张亮写的几篇博文(libevent源码深度剖析 h ...
随机推荐
- Oauth2.0认证
目录 Oauth Oauth2.0 客户端应用注册 授权码模式(authorization code)流程
- [转帖]大家分析分析C++ X64X86通用驱动读写API源码教程
//#include <windows.h>//#include <algorithm> //#include <string.h>//#include < ...
- 删除自定义服务 寒江孤钓<<windows 内核安全编程>> 学习笔记
书中第一章 成功启动first服务之后, 发现书中并没有介绍删除服务的方式, SRVINSTW工具中的移除服务功能,也无法找到我们要删除的服务, 于是,搜素了下,发现解决方法如下: 在cmd中输入 & ...
- 【python】Leetcode每日一题-逆波兰表达式求值
[python]Leetcode每日一题-逆波兰表达式求值 [题目描述] 根据 逆波兰表示法,求表达式的值. 有效的算符包括 +.-.*./ .每个运算对象可以是整数,也可以是另一个逆波兰表达式. 说 ...
- SpringBoot Restful
SpringBoot Restful 大家在做Web开发的过程中,method常用的值是get和post. 可事实上,method值还可以是put和delete等等其他值. 既然method值如此丰富 ...
- 北航OO(2020)第二单元博客作业
第二单元第一次作业 多线程设计策略 第一次作业的想法是设计三个线程:输入线程,调度器线程以及电梯线程.输入线程获取请求并发送给调度器线程:调度器线程通过查询电梯线程的状态(等待.停靠以及移动),并综合 ...
- CRM系统全方位管理企业
您在选择一款CRM系统的时候,首先要考虑销售团队的感受和意见.让CRM系统在帮助销售团队优化工作流程的同时,更好地对销售团队进行管理.销售人员每卖出一件商品,要从寻找筛选商机开始,经过沟通客户需求.满 ...
- C++基于armadillo im2col的实现
最近学习CNN,需要用到im2col这个函数,无奈网上没有多少使用armadillo的例子,而且armadillo库中似乎也没有这个函数,因此自己写了. im2col的原理网上一大把,我懒得写了. 1 ...
- Pulsar 下一代消息平台
引言 提起成熟的消息队列或消息引擎,毋庸置疑,大多数人的第一反应一定是 Kafka. Kafka 能够彻底满足海量数据场景下高吞吐.高并发需求,在短短几年内,已经被阿里.腾讯.百度.字节跳动.Netf ...
- Ansible命令行方式执行
Ansible ad-hoc 什么是ad-hoc? 临时命令,执行完不会保存,类似于批量执行命令. ansible的选项 -i # 指定主机清单 ansible rsync -m ping -i 1. ...