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代码 ...
随机推荐
- Google官方教程之Selling In-app Products
1.原文链接[需FQ]:http://developer.android.com/training/in-app-billing/index.html 2.平时对于英文文档都是大概读一下,现在翻译文章 ...
- 沉淀,再出发:结合案例看python
沉淀,再出发:结合案例看python 一.前言 关于python,如果不经过大型程序开发的洗礼,我们很难说自己已经懂得了python了,因此,我们需要通过稍微结构化的编程来学习python. 二.一个 ...
- 计算机应用基础教程作业flash动画 车辆工程 冯大昕
- scala流程控制语句以及方法和函数
1.if else表达式 scala中没有三目运算符,因为根本不需要.scala中if else表达式是有返回值的,如果if或者else返回的类型不一样,就返回Any类型(所有类型的公共超类型). 例 ...
- oracle imp 报12154错误解决办法
imp test/test@orcl file=/backup.dmp full=y --报错 imp test/test@127.0.0.1/orcl file=/backup.dmp full ...
- angularJs购物金额实例操作
<!DOCTYPE HTML> <html ng-app> <head> <meta http-equiv="Content-Type" ...
- 「bzoj 4025: 二分图」
题目 显然二分图没有奇环 于是考虑使用并查集维护一下看看是否存在奇环 我们可以考虑加权并查集,维护出\(x\)到\(fa_x\)的实际距离 由于我们只需要考虑奇偶性,于是我们处理出到根的路径异或一下就 ...
- P3177 [HAOI2015]树上染色
题目描述 有一棵点数为 N 的树,树边有边权.给你一个在 0~ N 之内的正整数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的N-K个点染成白色 . 将所有点染色后,你会获得黑点两两之 ...
- Kali-linux物理访问攻击
物理访问攻击与提升用户的权限类似.即当一个普通用户登录到系统中,破解本地其他用户账户的密码.在Linux中,普通用户可以通过su命令代替其他用户执行某些操作,意味着该用户能够在Linux/Unix系统 ...
- druid相关资料
官方资料直达地址: Druid 首页 https://github.com/alibaba/druid/wiki/%E9%A6%96%E9%A1%B5 Druid 常见问题 https://githu ...