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 源代码分析的更多相关文章

  1. Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6642463 在前面几篇文章中,我们详细介绍了A ...

  2. java TreeMap 源代码分析 平衡二叉树

    TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点. TreeSet 和 TreeMap 的关系 为了让大家了解 TreeMap 和 Tre ...

  3. 多线程之美2一ThreadLocal源代码分析

    目录结构 1.应用场景及作用 2.结构关系 2.1.三者关系类图 2.2.ThreadLocalMap结构图 2.3. 内存引用关系 2.4.存在内存泄漏原因 3.源码分析 3.1.重要代码片段 3. ...

  4. Java ConcurrentHashMap 源代码分析

    Java ConcurrentHashMap jdk1.8 之前用到过这个,但是一直不清楚原理,今天抽空看了一下代码 但是由于我一直在使用java8,试了半天,暂时还没复现过put死循环的bug 查了 ...

  5. Java HashMap 源代码分析

    Java HashMap jdk 1.8 Java8相对于java7来说HashMap变化比较大,在hash冲突严重的时候java7会退化为链表,Java8会退化为TreeMap 我们先来看一下类图: ...

  6. Java ArrayList 源代码分析

    Java ArrayList 之前曾经参考 数据结构与算法这本书写过ArrayList的demo,本来以为实现起来都差不多,今天抽空看了下jdk中的ArrayList的实现,差距还是很大啊 首先看一下 ...

  7. Android应用程序进程启动过程的源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址: http://blog.csdn.net/luoshengyang/article/details/6747696 Android 应用程序框架层创 ...

  8. java源代码分析----jvm.dll装载过程

    简述众所周知java.exe是java class文件的执行程序,但实际上java.exe程序只是一个执行的外壳,它会装载jvm.dll(windows下,以下皆以windows平台为例,linux下 ...

  9. Java源代码分析与生成

    源代码分析:可使用ANTLRANTLR是开源的语法分析器,可以用来构造自己的语言,或者对现有的语言进行语法分析. JavaParser 对Java代码进行分析 CodeModel 用于生成Java代码 ...

随机推荐

  1. 使用iCarousel的旋转木马效果请求图片

    使用iCarousel的旋转木马效果请求图片 https://github.com/nicklockwood/iCarousel 先看看效果: 源码如下: // // RootViewControll ...

  2. 沉淀,再出发:Docker的功能浅析

    沉淀,再出发:Docker的功能浅析 一.前言 这段时间一直在使用docker,发现docker的技术有很多,从最开始的将自己的程序打包成docker中的镜像,然后上传和下载镜像并使用,再到后来的在集 ...

  3. [零基础学JAVA]Java SE面向对象部分.面向对象基础(06)

    1.interface 接口 2.设计模式(工厂模式) 3.异常的捕获 java: //接口中定义的全是public,即使不声明也是public的,//如果一个类定义的时候全部由抽象方法和全局常量所组 ...

  4. form表单提交行为

    项目中有一个表单如下图,当我填完数据源名称这个input后,点击回车键本意是想跳到下一个input处,然而呢却触发了下面的添加这个按钮的事件,这是怎么回事呢,明明添加这个按钮并没有设置type=&qu ...

  5. BZOJ 1202 狡猾的商人 差分约束or带权并查集

    题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=1202 题目大意: 刁姹接到一个任务,为税务部门调查一位商人的账本,看看账本是不是伪造的 ...

  6. 「CF1025D Recovering BST」

    题目 郑州讲过的题了 发现这是一个二叉搜索树,给出的还是中序遍历,我们很自然的想到我们需要可以用一个\(f[i][j][k](k\in[i,j])\)来表示区间\([i,j]\)能不能形成以\(k\) ...

  7. 利用n 升级工具升级Node.js版本及在mac环境下的坑

    一.利用n 升级Node.js 最近在用NPM安装一个nodejs工具时发现,我的nodejs的版本有些旧了.这不是大问题,只要升级就可以了,当然,重新从nodejs.org最新版本是一种方法,但我想 ...

  8. pathinfo

    location ~ \.php { fastcgi_split_path_info ^((?U).+\.php)(/?.+)$; fastcgi_param PATH_INFO $fastcgi_p ...

  9. 关于ie8下监听input事件的不兼容问题。

    关于在ie8下,监听输入框的值变化的input事件不支持的解决办法: 很懒...直接上原文地址.... 原文地址:http://www.cnblogs.com/lhb25/archive/2012/1 ...

  10. week9:Recommender Systems

    Collaborative  filtering 的原理不是很理解? xi   是每一步电影的特征向量,表示浪漫/动作