ThreadLocal 和神奇的数字 0x61c88647
这篇文章会详细阐述ThreadLocal
的内部结构及其原理,以及神奇的0x61c88647
在Java 1.4之前,ThreadLocals会产生线程间的竞争,无法写出高性能的代码. Java 1.5改变了它的实现,下面详细阐述ThreadLocal
的内部结构和原理, 并分析为了解决散列表的冲突而引入的神奇的hash code: 0x61c88647
1 ThreadLocal
应用场景
先举个在平时工作中经常用到的场景, 一个web应用供登录用户通过浏览器访问,后台应用会获取用户的登录信息(如用户名),并对每个用户的访问做记录. 这是一个并发场景,每次请求都分配一个线程去处理这个请求,web容器一般都会有一个线程池,每次请求都会分配其中的一个空闲线程去处理用户的这次请求, 处理完毕后,线程归还线程池等待后续访问的线程分配.
当然,用户登录信息可以从当前请求request中获取,但是后台应用的多个地方可能都会需要用户登录信息, 一个解决办法是向这些所有用到的地方传递request参数,显然是麻烦的。另外一个办法就是利用ThreadLocal, 获取登录信息后把它放到当前线程中的ThradLocal变量中,任何需要的时候从当前线程中取就可以了,是不是很方便呢?
因此ThreadLocal的应用场景应该是实现在不同的线程存储不同的上下文信息的场合中,这样的场合最多的可能就是webapp中,引用stackoverflow中的一个回答:
ThreadLocal is most commonly used to implement per-request global variables in app servers or servlet containers where each user/client request is handled by a separate thread.
2 ThreadLocal
原理
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable.
ThreadLocal
instances are typically private static fields in classes that wish to associate state with a thread.
因此,ThreadLocal只是提供一个thread-local变量,这个变量于当前线程所独有, 每一个线程都有一个隶属与当前线程的thread-local变量
下面是ThreadLocal对外提供的四个方法:
protected T initialValue()
设置并返回当前线程变量的一个初始值set(T value)
将信息value放到当前线程的thread-local变量中T get()
是获取set(T value)
设置的值,如果没有则返回初始值remove()
移除线程中的这个thread-local变量
thread-local变量是怎么与当前线程Thread
关联的呢? 看一下Thread
源码,它有一个实例属性:
/**
* ThreadLocal values pertaining to this thread.
* This map is maintained by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
是的,就是ThreadLocal.ThreadLocalMap
对象(Thread和ThreadLocal类属于相同的包java.lang
). 看来它是用ThreadLocalMap
实现的,此时能看出ThreadLocalMap
是ThreadLocal
类中的一个静态内部类, 也可以看出上面说的thread-local变量其实就是这个threadLocals对象, 下面就看下这个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;
}
} /**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16; /**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table; /**
* Get the entry associated with key.
*/
private Entry getEntry(ThreadLocal key) {...} /**
* Set the value associated with key.
*/
private void set(ThreadLocal key, Object value) {...} // æé å1⁄2æ°åå ¶ä»ä ̧äoå1⁄2æ°çç¥
}
可以看出ThreadLocalMap
确实是一个map, 通过它的属性Entry[] table
实现,而Entry
的key是ThreadLocal
对象,value是要设置的值,
注意两点:
具体的ThreadLocalMap实例并不是ThreadLocal保持,而是每个Thread持有,且不同的Thread持有不同的ThreadLocalMap实例, 因此它们是不存在线程竞争的(不是一个全局的map), 另一个好处是每次线程死亡,所有map中引用到的对象都会随着这个Thread的死亡而被垃圾收集器一起收集
Entry
的key是对ThreadLocal的弱引用,当抛弃掉ThreadLocal对象时,垃圾收集器会忽略这个key的引用而清理掉ThreadLocal对象, 防止了内存泄漏
总上所述,可以用下面的结构图描述ThreadLocal的工作原理:
当向thread-local变量中设置value时(set(T value)
),获取当前Thread中的ThreadLocalMap,如果此时是null,则用ThreadLocal实例和value构建一个map设置到当前线程的属性threadLocals
中, 否则通过ThreadLocal对象作为key直接将ThreadLocal实例和value放到当前Thread已存在的map中(可能产生冲突,后面介绍)
当从ThreadLocal变量中获取value时(get()
), 获取当前Thread中的ThreadLocalMap, 如果为null则通过initialValue()
构建初始值同时利用这个初始值构建一个map到当前Thread中,最后返回这个初始值,否则从map中获取对应的Entry并返回value
通过原理分析可以看出,在使用ThreadLocal
是应该将它声明为public static, 即所有线程共用一个ThreadLocal实例,而不是每一个线程来临时都要新创建一个ThreadLocal对象, Java Doc也建议,ThreadLocal应当声明为public static.
3 碰撞解决与神奇的 0x61c88647
既然ThreadLocal
用map就避免不了冲突的产生
3.1 碰撞避免和解决
这里碰撞其实有两种类型
只有一个ThreadLocal实例的时候(上面推荐的做法),当向thread-local变量中设置多个值的时产生的碰撞,碰撞解决是通过开放定址法, 且是线性探测(linear-probe)
多个ThreadLocal实例的时候,最极端的是每个线程都
new
一个ThreadLocal实例,此时利用特殊的哈希码0x61c88647
大大降低碰撞的几率, 同时利用开放定址法处理碰撞
3.2 神奇的 0x61c88647
注意 0x61c88647
的利用主要是为了多个ThreadLocal
实例的情况下用的
从ThreadLocal
源码中找出这个哈希码所在的地方
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached
* to each thread (Thread.threadLocals and inheritableThreadLocals).
* The ThreadLocal objects act as keys, searched via threadLocalHashCode.
* This is a custom hash code (useful only within ThreadLocalMaps) that
* eliminates collisions in the common case where consecutively
* constructed ThreadLocals are used by the same threads,
* while remaining well-behaved in less common cases.
*/
private final int threadLocalHashCode = nextHashCode(); /**
* The next hash code to be given out. Updated atomically.
* Starts at zero.
*/
private static AtomicInteger nextHashCode = new AtomicInteger(); /**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647; /**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
注意实例变量threadLocalHashCode
, 每当创建ThreadLocal
实例时这个值都会累加 0x61c88647
, 目的在上面的注释中已经写的很清楚了:为了让哈希码能均匀的分布在2的N次方的数组里, 即 Entry[] table
下面来看一下ThreadLocal
怎么使用的这个 threadLocalHashCode
哈希码的,下面是ThreadLocalMap
静态内部类中的set方法的部分代码:
// Set the value associated with key.
private void set(ThreadLocal key, Object value) { Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null;
e = tab[i = nextIndex(i, len)]) {...} ...
key.threadLocalHashCode & (len-1)
这么用是什么意思? 先看一下table
数组的长度吧:
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
哇,ThreadLocalMap
中 Entry[] table
的大小必须是2的N次方呀(len = 2^N),那 len-1
的二进制表示就是低位连续的N个1, 那 key.threadLocalHashCode & (len-1)
的值就是 threadLocalHashCode
的低N位, 这样就能均匀的产生均匀的分布? 我用python做个实验吧
>>> HASH_INCREMENT = 0x61c88647
>>> def magic_hash(n):
... for i in range(n):
... nextHashCode = i * HASH_INCREMENT + HASH_INCREMENT
... print nextHashCode & (n - 1),
...
>>> magic_hash(16)
7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0
>>> magic_hash(32)
7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0
产生的哈希码分布真的是很均匀,而且没有任何冲突啊, 太神奇了, javaspecialists中的一篇文章有对它的一些描述:
This number represents the golden ratio (sqrt(5)-1) times two to the power of 31 ((sqrt(5)-1) * (2^31)). The result is then a golden number, either 2654435769 or -1640531527.
以及
We established thus that the
HASH_INCREMENT
has something to do with fibonacci hashing, using the golden ratio. If we look carefully at the way that hashing is done in theThreadLocalMap
, we see why this is necessary. The standardjava.util.HashMap
uses linked lists to resolve clashes. TheThreadLocalMap
simply looks for the next available space and inserts the element there. It finds the first space by bit masking, thus only the lower few bits are significant. If the first space is full, it simply puts the element in the next available space. TheHASH_INCREMENT
spaces the keys out in the sparce hash table, so that the possibility of finding a value next to ours is reduced.
这与fibonacci hashing(斐波那契散列法)以及黄金分割有关,具体可研究中的 6.4 节Hashing部分
4 线程池时使用 ThreadLocal
web容器(如tomcat)一般都是使用线程池处理用户到请求, 此时用ThreadLocal
要特别注意内存泄漏的问题, 一个请求结束了,处理它的线程也结束,但此时这个线程并没有死掉,它只是归还到了线程池中,这时候应该清理掉属于它的ThreadLocal信息,
remove()
线程结束时应当调用ThreadLocal的这个方法清理掉thread-local变量
相关资料:
ThreadLocal 和神奇的数字 0x61c88647的更多相关文章
- 神奇的魔法数字0x61c88647
来源JDK源码,产生的数字分布很均匀 用法代码如下. # -*- coding: utf-8 -*- HASH_INCREMENT = 0x61c88647 def magic_hash(n): fo ...
- 结合源码谈谈ThreadLocal!
目录 ThreadLocal的作用 ThreadLocal 1.对象初始化 2.获取变量 3.设置变量 4.移除变量 ThreadLocalMap 1.Entry 2.初始化 3.获取Entry 4. ...
- [LeetCode] 878. Nth Magical Number 第N个神奇数字
A positive integer is magical if it is divisible by either A or B. Return the N-th magical number. ...
- 深入源码理解ThreadLocal和ThreadLocalMap
一.ThreadLoacl的理解: 官方的讲: ThreadLocal是一个本地线程副本变量工具类,主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰 通俗的讲: Thre ...
- 深入理解 ThreadLocal
前言 上篇文章 深入理解 Handler 消息机制 中提到了获取线程的 Looper 是通过 ThreadLocal 来实现的: public static @Nullable Looper myLo ...
- ThreadLocal原理分析与代码验证
ThreadLocal提供了线程安全的数据存储和访问方式,利用不带key的get和set方法,居然能做到线程之间隔离,非常神奇. 比如 ThreadLocal<String> thread ...
- Java面试题必备知识之ThreadLocal
老套路,先列举下关于ThreadLocal常见的疑问,希望可以通过这篇学习笔记来解决这几个问题: ThreadLocal是用来解决什么问题的? 如何使用ThreadLocal? ThreadLocal ...
- 多线程之美2一ThreadLocal源代码分析
目录结构 1.应用场景及作用 2.结构关系 2.1.三者关系类图 2.2.ThreadLocalMap结构图 2.3. 内存引用关系 2.4.存在内存泄漏原因 3.源码分析 3.1.重要代码片段 3. ...
- PAT 1019. 数字黑洞 (20)
给定任一个各位数字不完全相同的4位正整数,如果我们先把4个数字按非递增排序,再按非递减排序,然后用第1个数字减第2个数字,将得到一个新的数字.一直重复这样做,我们很快会停在有"数字黑洞&qu ...
随机推荐
- openvswitch 流表测试 ovs-appctl
[root@ostack170 ~]# ovs-appctl ofproto/trace br-mirror in_port=,dl_vlan=,dl_src=:ea:cb:5d:e4:ee,dl_d ...
- govendor 使用
govendor是go语言依赖管理工具,推荐使用 https://github.com/kardianos/govendor 这个版本. go get -u -v github.com/kardian ...
- 问卷星的数据导入spss后变量乱码如何处理?
一般是字符编码问题.打开一个空的SPSS数据集,选择[编辑]-[选项]-[常规]-[数据和语法的字符编码].修改下当前的编码系统,原来是第一种就换成第二种,原来是第二种就换成第一种,打开一个数据再看看 ...
- muduo源码解析4-exception类
exception class exception:public std::exception { }; 作用: 实现了一个异常类,继承于std::exception,主要用于实现打印线程调用栈信息. ...
- 企业站如何做长尾关键词seo优化
http://www.wocaoseo.com/thread-315-1-1.html 很多企业站,优化到一定程度后网站的流量很快就上去了,但是之后网站就无法更进一步.那么对于普通中小型企业站 ...
- 《MySQL数据库》MySQL主从复制搭建与原理
前言 主从复制:两台或者更多的数据库实例,通过二进制日志,实现数据同步.为什么需要主从复制,主从复制的作用是什么,答:为了预防灾难. 搭建 第一步:准备多实例环境.如何创建多实例见: 第二步:确保每一 ...
- win环境下安装配置openCV-4.3.0
win环境下安装openCV-4.3.0 首先下载 推荐国内镜像 官网太太太慢了 附上 下载地址 下载之后打开exe解压到目录都是常规操作 环境变量的配置 依次打开到系统变量的path 新建一个路径为 ...
- MonoBehaviour生命周期与对象数据池应用
预热游戏对象: tempObject = Instantiate(cubePrefab) as GameObject ; tempObject .SetActive( false ); 游戏对象tem ...
- Unity NGUI C#性能优化
建议读者先看这篇博文:http://blog.csdn.net/zzxiang1985/article/details/43339273,有些技术已经变了,比如第1招,unity5的打包机制已经变许多 ...
- 16_Python的包package
1.包的概述 1.包是将模块一文件夹的组织形式进行分组管理的方法,一系列模块进行分类管理有利于防止命名冲突 2.包是一个包含多个模块的特色目录,目录下有一个特色的文件__init__.py 3.包的命 ...