Java ThreadLocal 源代码分析
Java ThreadLocal
之前在写SSM项目的时候使用过一个叫PageHelper的插件
可以自动完成分页而不用手动写SQL limit
用起来大概是这样的

最开始的时候觉得很困惑,因为直接使用静态成员函数,那么就意味着如果有别的线程同时执行,可能会导致一些并发错误
答案是不会,因为PageHelper内部实现是使用到了ThreadLocal这个对象的,每个线程单独使用一个Page对象

百度了一下,发现ThreadLocal是一个提供类似线程内部的局部变量
我们来看一下ThreadLocal的源代码:初始化的时候涉及到这几个变量
private static AtomicInteger nextHashCode =
new AtomicInteger();
private final int threadLocalHashCode = nextHashCode();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
每次创建一个ThreadLocal的时候就会给这个?ThreadLocal分配一个hashcode
为什么不是每次increateAndGet 注释里面有解释:
连续生成的散列码的区别
隐式顺序线程局部ID进入近最优分布
两个大小表的幂的乘法哈希值。
首先看一下get方法
//ThreadLocal.java
public T get() {
//首先获取当前的Thread
Thread t = Thread.currentThread();
//通过当前Thread尝试获取 ThreadLocalMap
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;
}
}
//当前Thread并没有初始化map或者Thread值,进行初始化操作
return setInitialValue();
}
通过名字可以知道ThreadLocalMap似乎是个Map
我们先查看一下getMap

发现这个map是存储在Thread 里面的,包作用域,用户不可见
//Tread.java class Thread
ThreadLocal.ThreadLocalMap threadLocals = null;
我们现在看一下这个ThreadLocalMap的定义
static class ThreadLocalMap {
//定义键值对,是一个WeakReference
static class Entry extends WeakReference<ThreadLocal<?>> {
//ThreadLocal里面保存变量的值
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//默认初始容量
private static final int INITIAL_CAPACITY = 16;
//桶
private Entry[] table;
private int size = 0;
初始化:
我们可以看到第一次初始化的时候是使用firstKey的threadLocalHashCode(firstKey指的是外部的this)刚才提到的初始化的时候分配的一个hashcode,具体桶的位置跟hashmap类似都是(桶-1)&hashcode
//ThreadLocal.java
//static class ThreadLocalMap
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);
}
ThreadLocalMap里面的 get方法
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);
}
ThreadLocalMap解决hash冲突的方法并不是用链表,而是使用线性探测法
这也就解释了为什么分配的hashcode不应该是连续的原因,否则一旦出现hash冲突,线性探测找到一个可用的空间或者key对应的值非常艰难
我们来看一下是如何实现线性探测的:
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//当这个位置不是空的时候继续探测
while (e != null) {
ThreadLocal<?> k = e.get();
//判断key是不是相等,如果相等说明找到了
if (k == key)
return e;
//这里不太理解,查了一下别人的分析
//k==null说明这个key已经被释放掉,需要清理掉
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);//((i + 1 < len) ? i + 1 : 0);就是下一个空间,如果到末尾就从头开始
e = tab[i];//下一个空间里面的值
}
return null;
}
//这个函数不太理解
//我猜大概意思就是需要别的地方有一个ThreadLocal引用否则ThreadLocal可能被清理掉
//弱引用会被GC标记存活
//这个做的应该是标记为null之后,把后面的值放到前面,否则再次get的时候碰到null就找不到了
/**
* 这个函数是ThreadLocal中核心清理函数,它做的事情很简单:
* 就是从staleSlot开始遍历,将无效(弱引用指向对象被回收)清理,即对应entry中的value置为null,将指向这个entry的table[i]置为null,直到扫到空entry。
* 另外,在过程中还会对非空的entry作rehash。
* 可以说这个函数的作用就是从staleSlot开始清理连续段中的slot(断开强引用,rehash slot等)
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
//直接置null
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
//开始从这个位置开始线性探测
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
//每次线性探测一个格子直到找到null
ThreadLocal<?> k = e.get();
//key为null说明需要清理掉
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
//重新找到标记桶的位置
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
//清理当前位置
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
//线性探测一个可以用的位置,然后把自己放进去
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
并发安全问题:
代码中没有使用到任何锁和同步,为什么还是安全的
因为每个线程操作的ThreadLocalMap都是每个线程自带的,当然不用同步啦
具体使用:比如说解决SimpleDateFormat的问题
这样每个线程只会创建一个
具体的线性探测hash可以看着里面的图
https://www.cnblogs.com/micrari/p/6790229.html
Java ThreadLocal 源代码分析的更多相关文章
- Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6642463 在前面几篇文章中,我们详细介绍了A ...
- java TreeMap 源代码分析 平衡二叉树
TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点. TreeSet 和 TreeMap 的关系 为了让大家了解 TreeMap 和 Tre ...
- 多线程之美2一ThreadLocal源代码分析
目录结构 1.应用场景及作用 2.结构关系 2.1.三者关系类图 2.2.ThreadLocalMap结构图 2.3. 内存引用关系 2.4.存在内存泄漏原因 3.源码分析 3.1.重要代码片段 3. ...
- Java ConcurrentHashMap 源代码分析
Java ConcurrentHashMap jdk1.8 之前用到过这个,但是一直不清楚原理,今天抽空看了一下代码 但是由于我一直在使用java8,试了半天,暂时还没复现过put死循环的bug 查了 ...
- Java HashMap 源代码分析
Java HashMap jdk 1.8 Java8相对于java7来说HashMap变化比较大,在hash冲突严重的时候java7会退化为链表,Java8会退化为TreeMap 我们先来看一下类图: ...
- Java ArrayList 源代码分析
Java ArrayList 之前曾经参考 数据结构与算法这本书写过ArrayList的demo,本来以为实现起来都差不多,今天抽空看了下jdk中的ArrayList的实现,差距还是很大啊 首先看一下 ...
- Android应用程序进程启动过程的源代码分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址: http://blog.csdn.net/luoshengyang/article/details/6747696 Android 应用程序框架层创 ...
- java源代码分析----jvm.dll装载过程
简述众所周知java.exe是java class文件的执行程序,但实际上java.exe程序只是一个执行的外壳,它会装载jvm.dll(windows下,以下皆以windows平台为例,linux下 ...
- Java源代码分析与生成
源代码分析:可使用ANTLRANTLR是开源的语法分析器,可以用来构造自己的语言,或者对现有的语言进行语法分析. JavaParser 对Java代码进行分析 CodeModel 用于生成Java代码 ...
随机推荐
- iOS9 News 应用
iOS9 News 应用 iOS9 中国区虽然没有 News 应用,但最新的开发工具中是有的,以下是笔者截取的模拟器gif图,供君欣赏:
- [零基础学JAVA]Java SE面向对象部分.面向对象基础(05)
1.继承 2.多态 3.final 4.重载与覆写 5. this/super 6.抽象类 7.接口 java: class Person{ private String name; priva ...
- canvas图形库
总结了一些canvas绘制2d图形的方法,记录在博客中,以便需要的同学参考,也便于日后加深记忆. 1. 圆角矩形: 如上图:w表示矩形的宽,h表示矩形的高,r表示矩形圆角的半径.整个矩形在画布中,(0 ...
- 巧用DNSlog实现无回显注入
测试一些网站的时候,一些注入都是无回显的,我们可以写脚本来进行盲注,但有些网站会ban掉我们的ip,这样我们可以通过设置ip代理池解决, 但是盲注往往效率很低,所以产生了DNSlog注入.具体原理如下 ...
- Hadoop 解除 NameNode is in safe mode
运行Hadoop程序时,有时候会报以下错误: org.apache.hadoop.dfs.SafeModeException: Cannot delete /user/hadoop/input. Na ...
- 监听器中spring注入相关的问题
问题描述: 需求是要求在项目启动自动触发一个service中的线程的操作,使用监听器来实现,但是自定义监听器中spring注解service失败,通过WebApplicationContextUtil ...
- leetcode 1.Two Sum 、167. Two Sum II - Input array is sorted 、15. 3Sum 、16. 3Sum Closest 、 18. 4Sum 、653. Two Sum IV - Input is a BST
1.two sum 用hash来存储数值和对应的位置索引,通过target-当前值来获得需要的值,然后再hash中寻找 错误代码1: Input:[3,2,4]6Output:[0,0]Expecte ...
- 【转】如何开发自己的HttpServer-NanoHttpd源码解读
现在作为一个开发人员,http server相关的内容已经是无论如何都要了解的知识了.用curl发一个请求,配置一下apache,部署一个web server对我们来说都不是很难,但要想搞清楚这些背后 ...
- 如何在Ubuntu中添加中文输入法
首先打开终端,输入以下命令 打开终端输入以下命令 弹出设置IBus首选项对话框,单击输入法文本框,再单击添加按钮,选中汉语中的拼音,单击添加,设置完毕. 最后单击输入法图标的选项中的首选项中的拼音模式 ...
- 移动端h5列表页上拉加载更多
背景 上星期公司要求做一个回收书籍的h5给安卓用,里面有一个功能是回收记录列表.设计师那边出的稿子是没有要求分页或者是上拉刷新的,但是众所周知,列表页数据很多的情况下,h5加载是很慢的.所以我一开始是 ...