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代码 ...
随机推荐
- SpringBoot 中解决跨域请求
CORS 理解 同源策略是web浏览器实现的一个重要的安全概念,它防止JavaScript代码对不同的来源(例如,不同的域)发出请求,而不是它所服务的来源.虽然同源策略有效地防止来自不同来源的资源,但 ...
- Genymotion安卓模拟器和VirtualBox虚拟机安装、配置、测试(win7_64bit)
1.概述 VirtualBox是一个优秀的虚拟机软件,它可以在电脑上提供另一个操作系统的运行环境,使多个系统同时运行.VirtualBox支持的操作系统包括Windows.Mac OS X.Linux ...
- 021.10 IO流 打印流
内容:PrintStream:字节流 PrintWriter:字符流 PrintStream public static void main(String[] args) throws IOEx ...
- PHP版本解密openrtb中的价格
Decrypt Price Confirmations 原文地址 https://developers.google.com/ad-exchange/rtb/response-guide/decryp ...
- Mac下安装Spark
1.Scala 官网下载scala安装包后解压,路径随意. 编辑/etc/bash_profile添加$SCALA_HOME并修改相应PATH 2.SSH无密码登陆 ssh-keygen -t rsa ...
- 【NOI2008】假面舞会
题目描述 一年一度的假面舞会又开始了,栋栋也兴致勃勃的参加了今年的舞会. 今年的面具都是主办方特别定制的.每个参加舞会的人都可以在入场时选择一 个自己喜欢的面具.每个面具都有一个编号,主办方会把此编号 ...
- MacBook搭建go语言开发环境
mac下要安装 go 最简单的方式是通过 homebrew 直接执行: brew update && brew upgrade brew install go 安装完成后需要指定 GO ...
- VIM之模式
1.模式介绍: 在真正开始使用VIM之前,你必须先了解VIM的模式,否则在 VIM 面前你可能会手足无措.VIM是有模式 编辑器,这意味着 VIM 有多种不同的工作模式,在不同的工作模式下用户相同的操 ...
- leetcode231 2的幂 leetcode342 4的幂 leetcode326 3的幂
1.2的幂 正确写法: class Solution { public: bool isPowerOfTwo(int n) { ) return false; )) == ; } }; 错误写法1: ...
- mysql自增ID过大修改方法
执行sql: alter table table_name AUTO_INCREMENT=100