Java 线程 — ThreadLocal
ThreadLocal
先来看看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 (e.g., a user ID or Transaction ID).
翻译过来就是:ThreadLocal提供了线程级的变量,这个变量和其他的使用set、get访问的变量不一样,ThreadLocal针对每个线程会为该变量维护经过单独初始化的副本。ThreadLocal实例希望定义为private static。
从注释可以看出ThreadLocal解决的问题是:
- 单线程的变量共享
单线程变量共享
如果是变量共享为什么不用一个全局变量就好呢?主要是因为ThreadLocal为每个线程维护经过单独初始化的变量副本,但是普通的变量访问到的就是同一个。
上面的注释也说了,ThreadLocal会为每个线程维护经过单独初始化的变量副本,每个线程访问的都是自己的副本,所以各个线程之间不会相互影响,达到隔离的作用
线程同步解决的是多线程访问共享资源的问题,但是ThreadLocal本身并不是用来多线程之间共享的,只是用来单线程共享的,所以ThreadLocal和线程同步根本不是一回事儿
实现原理
每个Thread都有一个ThreadLocal.ThreadLocalMap,因为Thread类里面有一个该类的对象,用来存放该线程中所有的ThreadLocal类型的变量
ThreadLocalMap里面有一个Entry(继承自WeakReference,一个对象保存一个键值对)数组,根据key(这里就是ThreadLocal变量本身)的哈希值将value(这里就是需要保存的数据)散列到数组中
初次调用threadLocal.get的时候如果ThreadLocalMap尚未初始化,会调用createMap初始化
ThreadLocalMap初始化
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
// 如果map未初始化
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
// 新建初始大小为16的数组
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
// 设置扩容的阈值
setThreshold(INITIAL_CAPACITY);
}
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);
}
// 如果有碰撞则向后查找
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal k = e.get();
if (k == key)
return e;
if (k == null)
// 如果key为null则进行一次清除
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
set
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 使用的是开放地址法解决冲突,如果发生碰撞则向后查找
// 如果得到的i位置已经有值,那么就向后一个单位尝试填充
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
// 如果是相同的key就替换,说明同一个对象中的
// 同一个threadLocal变量
if (k == key) {
e.value = value;
return;
}
// 因为是弱引用,ThreadLocal已经被回收,所以key就是null,将value放在这个位置
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 直到找到一个元素为空的位置(e == null),
// 每新占用一个数组位置(上面都是在替换原来元素或者替换
// 已经被移除的元素,size已经加过的)就要判断是否需要进行扩容
tab[i] = new Entry(key, value);
int sz = ++size;
// 如果没有清除数组中的元素并且元素个数已经大于等于阈值threshold则进行扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
// 扫描数组清除陈旧的数据,但并不是全部扫描,而是log2(n)对数扫描
// 在全部扫描和不扫描之间取一个折中
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
// 无符号右移一位,相当于每次除2取整
} while ( (n >>>= 1) != 0);
return removed;
}
// 在staleSlot到下一个数组元素为null之间,清空陈旧(key为null)的元素,
// 重新散列非陈旧的元素
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
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)) {
ThreadLocal k = e.get();
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;
}
private void remove(ThreadLocal key) {
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)]) {
if (e.get() == key) {
e.clear();
// 将若引用置为null之后,进行一次清除
expungeStaleEntry(i);
return;
}
}
}
Java 中的引用
各种不同的引用的区别就是引用到的对象被垃圾回收的时机不同
- 强引用:如果有强引用到一个对象,那么该对象不会被回收
- 弱引用:只有弱引用链接的对象,在系统进行GC的时候就会被垃圾回收
- 软引用:只有内存不够的时候,在会被GC回收
- 虚引用:任何时候都可以被回收
神奇的0x61c88647
在ThreadLocalMap的hash算法中,很少发生碰撞,原因在于精巧的hash算法
private final int threadLocalHashCode = nextHashCode();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
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);
}
这里最不解的是为什么要用0x61c88647这个数?怎么来的?
0x61c88647换算成十进制是1640531527,计算方法如下
1640531527 = (long) ((1L << 31) * (Math.sqrt(5) - 1))
(Math.sqrt(5) - 1) / 2 是黄金分割数
这种hash方法是Donald Knuth在 The Art of Computer Programming 中提出,不明觉厉
问题
清除stale数组元素的标准就是 key == null,什么时候ThreadLocal.key为null?
key为null:因为key是ThreadLocal的弱引用,当ThreadLocal没有强引用的时候,ThreadLocal变量可能会被回收,这个时候出现了key为null的情况
Java 线程 — ThreadLocal的更多相关文章
- Java线程与并发库高级应用-线程范围内共享数据ThreadLocal类
1.线程范围内共享变量 1.1 前奏: 使用一个Map来实现线程范围内共享变量 public class ThreadScopeShareData { static Map<Thread, In ...
- java面试题之什么是线程ThreadLocal?作用是什么?
定义:线程局部变量是局限于线程内的变量,属于线程自身所有,不在多个线程间共享.java提供ThreadLocal类来支持线程局部变量,是一个实现线程安全的方式. 任何线程局部变量一旦在工作完成后没有释 ...
- Java线程变量问题-ThreadLocal
关于Java线程问题,在博客上看到一篇文章挺好的: https://blog.csdn.net/w172087242/article/details/83375022#23_ThreadLocal_1 ...
- Java线程并发:知识点
Java线程并发:知识点 发布:一个对象是使它能够被当前范围之外的代码所引用: 常见形式:将对象的的引用存储到公共静态域:非私有方法中返回引用:发布内部类实例,包含引用. 逃逸:在对象尚未准备 ...
- Java线程的概念
1. 计算机系统 使用高速缓存来作为内存与处理器之间的缓冲,将运算需要用到的数据复制到缓存中,让计算能快速进行:当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了. 缓 ...
- 【转载】 Java线程面试题 Top 50
Java线程面试题 Top 50 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员 的欢迎.大多数待遇丰厚的J ...
- java线程内存模型,线程、工作内存、主内存
转自:http://rainyear.iteye.com/blog/1734311 java线程内存模型 线程.工作内存.主内存三者之间的交互关系图: key edeas 所有线程共享主内存 每个线程 ...
- Java线程的5个使用技巧
萝卜白菜各有所爱.像小编我就喜欢Java.学无止境,这也是我喜欢它的一个原因.日常工作中你所用到的工具,通常都有些你从来没有了解过的东西,比方说某个方法或者是一些有趣的用法.比如说线程.没错,就是线程 ...
- 【Java】ThreadLocal细节分析
ThreadLocal通过中文解释就是线程本地变量,是线程的一个局部变量.根据哲学家黑格尔“的存在即合理”的说法,ThreadLocal的出现肯定是有它的意义,它的出现也是因为多线程的一个产物.Thr ...
随机推荐
- Merge在Sqlserver使用例子说明
---文章 MatchInt的方式 Create table SourceTable([ID] int,[Desc] varchar(50)); Create table TargetTable([I ...
- CMM能力成熟度模型
CMM把软件企业的过程管理能力划分为5个等级: 1 .初始级:个别的.混乱无序的过程,软件缺乏定义,项目的成功严重依赖于某几个关键人员的努力.软件质量由个人的开发经验来保障. 2.可重复级 实施了基 ...
- Usart的单线半双工模式(stm32F10x系列)
这两天折腾CTS/RTS硬件流控,看到说232协议的CTS/RTS只是用来做半双工换向使用的.正好手头上有块stm32的板子,看了看stm32的Usart,竟然发现支持的是单线半双工.232里面毕竟4 ...
- Hbase随笔
大数据时代的数据量是超大规模的,传统的关系数据库已经很难存储和管理这些数据了,为了存储海量数据,我们有了HDFS,它可以把成千上万台服务器上的硬盘聚集成一块超级大的硬盘,为了让这些数据产生价值,我们有 ...
- 读Lua游戏开发实践指南
11月11日开读,到今天正好一个月. 起因是被裁员之后,发现很多公司都在使用lua编写cocos2d-x游戏,原因是上手快,技术人员比较便宜. 如果引擎封装比较好,几乎在lua里写写基本逻辑就行了,不 ...
- 条件随机场理论分析CRF(Conditional Random Field)
- 使用VirtualBox自带管理工具命令为虚拟磁盘扩展空间
VirtualBox虚拟磁盘空间不够了,默认10G.想扩大,图形界面下没有找到可操作菜单.Google了一下用 Vbox自带的命令工具VBoxManage即可解决. C:\Program Files\ ...
- 【C++】DDX_Control、SubclassWindow和SubclassDlgItem的区别
在自绘窗口的时候,子类化是MFC最常用的窗体技术之一.什么是子类化?窗口子类化就是创建一个新的窗口函数代替原来的窗口函数. Subclass(子类化)是MFC中最常用的窗体技术之一.子类化完成两个工作 ...
- Struts2+Spring+Hibernate(SSH)框架的搭建
首先需要下载struts2 ,spring4,hibernate5 的资源包; struts2资源包下载路径:http://www.apache.org/spring资源包下载路径:http://p ...
- Linux之脚本安装软件
查看启动程序 ps aux 准备工作 1.保证yum源正常使用 2.关闭SELinux和防火墙 下载脚本文件包 解压缩 运行 ./centors.sh