感谢原文作者:Yuicon

原文链接:https://segmentfault.com/a/1190000016705955


在多线程环境下,访问非线程安全的变量时必须进行线程同步,例如使用 synchronized 方式访问HashMap实例。但是同步访问会降低并发性,影响系统性能。这时候就可以用空间换时间,如果我们给每个线程都分配一个独立的变量,就可以用非同步的方式使用非线程安全的变量,我们称这种变量为线程局部变量。

顾名思义,线程局部变量是指每个线程都有一份属于自己独立的变量副本,不会像普通局部变量一样可以被其他线程访问到。Java并没有提供语言级的线程局部变量,而是在类库里提供了线程局部变量的功能,也就是这次的主角ThreadLocal类。

ThreadLocal的使用




Java8版本的ThreadLocal有上图所示的4个public方法和一个protected的方法,第一个方法用于返回初始值,默认是null。第二个静态方法withInitial(Supplier<? extends S> supplier)是Java8版本新添加的,后面三个实例方法则非常的简单。

在Java8之前,使用ThreadLocal时想要设置初始值时需要继承ThreadLocal类覆盖protected T initialValue()方法才行,例如:

ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};

在Java8版本可以使用新添加的静态方法withInitial(Supplier<? extends S> supplier),非常方便的设置初始值,例如:

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

System.out.println(threadLocal.get());
threadLocal.set(16);
System.out.println(threadLocal.get());
threadLocal.remove();
System.out.println(threadLocal.get()); // 同一个线程的输出
0
16
0 Process finished with exit code 0

ThreadLocal的原理


那么ThreadLocal是怎么实现线程局部变量的功能的呢?其实ThreadLocal的基本原理并没有十分复杂。ThreadLocal在内部定义了一个静态类ThreadLocalMapThreadLocalMap的键为ThreadLocal对象,ThreadLocalMap的值就是ThreadLocal存储的值,不过这个ThreadLocalMap是在Thread类里维护的。我们来看一下ThreadLocal的部分源码:

    // ThreadLocal的set方法
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取Map
ThreadLocalMap map = getMap(t);
if (map != null)
// 设置值
map.set(this, value);
else
// 初始化Map
createMap(t, value);
} // ThreadLocal的createMap方法
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
} // Thread类定义的实例域
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

可以看出ThreadLocal的核心实现就是ThreadLocalMap的实现了,ThreadLocalMap内部声明了一个Entry类来存储数据:

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

ThreadLocalMap的实现与HashMap的实现有相似的地方,比如同样是使用数组存储数据和自动扩容,不同的是hash算法与hash碰撞后的处理不一样。

        // ThreadLocalMap的set方法
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table;
int len = tab.length;
// 计算在Entry[]中的索引,每个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647
int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 如果键已存在就更新值
if (k == key) {
e.value = value;
return;
}
// 代替无效的键
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
} tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
} private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}

可以看到ThreadLocalMapEntry[]数组当成一个圆环。从计算出来的索引位置开始,如果该索引已经有数据了就判断Key是否相同,相同就更新值。否则就直到找到一个空的位置把值放进去。获取值的时候也类似,从计算出来的索引位置开始一个一个检查Key是否相同,这样hash碰撞比较多的话可能性能就不是很好。

ThreadLocal的应用


ThreadLocal的应用是非常广的,比如Java工程师非常熟悉的Spring框架中就使用了ThreadLocal来把非线程安全的状态性对象封装起来,所以我们可以把绝大部分的Bean声明为singleton作用域。我们在编写多线程代码时也可以想想是用同步的方式访问非线程安全的状态性对象比较好,还是使用ThreadLocal把非线程安全的状态性对象封装起来更好。

后记


本来下定决心准备一周一篇的,结果偷懒了一次后赶上了公司旅游。这一下子摸了两篇,只能后面慢慢补了……ThreadLocal我很早就看到过了,一直没什么实感,直到在《精通Spring 4.X 企业应用开发实战》看到在Spring中的应用后才发现,我从来没想过为什么Spring里的Dao类可以声明为单例作用域……没有举一反三的能力就只能多看书了,活到老学到老。

参考资料:


  • 《Java核心技术 卷一》
  • 《精通Spring 4.X 企业应用开发实战》

深层次原理

https://www.jianshu.com/p/3c5d7f09dfbd

最新文章

  1. fir.im Weekly - TouchBar 从入门到开发
  2. 创建型模式之Builder模式及实现
  3. 第一种SUSE Linux IP设置方法
  4. matlab工具箱之人眼检测+meanshift跟踪算法--人眼跟踪
  5. GitHub Android Libraries Top 100 简介
  6. (转)qsort和sort
  7. App软件开发的完整在线流程(一看就懂)
  8. vi用法
  9. php部分--数组(包含指针思想遍历数组);
  10. [JavaEE] Eclipse中web-inf和meta-inf文件夹的信息
  11. Android 动态改变布局属性RelativeLayout.LayoutParams.addRule()
  12. msvcp110.dll丢失
  13. oracle 时间比较查询
  14. UVA 12545 Bits Equalizer
  15. haproxy hdr_beg 配置
  16. iOS label换行 自适应
  17. python-装饰器简述
  18. hdu 2254(矩阵)
  19. Python Note1: Pycharm的安装与使用
  20. 【BZOJ4155】[Ipsc2015]Humble Captains

热门文章

  1. CS学习资料百度云链接
  2. linux 之 nginx安装步骤
  3. 初识python: 装饰器
  4. Redis_客户端命令和数据操作(3)
  5. vue-router 两个子路由之间相互跳转时出错
  6. C# 使用vs2017 创建类 时 注意点
  7. vim 安装使用 pathogen
  8. Linux上天之路(九)之文件和文件夹的权限
  9. 离线环境安装使用 Ansible
  10. springBoot--原理分析