Java多线程基础-ThreadLocal
感谢原文作者: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
在内部定义了一个静态类ThreadLocalMap
,ThreadLocalMap
的键为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);
}
可以看到ThreadLocalMap
把Entry[]
数组当成一个圆环。从计算出来的索引位置开始,如果该索引已经有数据了就判断Key
是否相同,相同就更新值。否则就直到找到一个空的位置把值放进去。获取值的时候也类似,从计算出来的索引位置开始一个一个检查Key
是否相同,这样hash
碰撞比较多的话可能性能就不是很好。
ThreadLocal的应用
ThreadLocal
的应用是非常广的,比如Java
工程师非常熟悉的Spring
框架中就使用了ThreadLocal
来把非线程安全的状态性对象封装起来,所以我们可以把绝大部分的Bean
声明为singleton
作用域。我们在编写多线程代码时也可以想想是用同步的方式访问非线程安全的状态性对象比较好,还是使用ThreadLocal
把非线程安全的状态性对象封装起来更好。
后记
本来下定决心准备一周一篇的,结果偷懒了一次后赶上了公司旅游。这一下子摸了两篇,只能后面慢慢补了……ThreadLocal
我很早就看到过了,一直没什么实感,直到在《精通Spring 4.X 企业应用开发实战》看到在Spring
中的应用后才发现,我从来没想过为什么Spring
里的Dao
类可以声明为单例作用域……没有举一反三的能力就只能多看书了,活到老学到老。
参考资料:
- 《Java核心技术 卷一》
- 《精通Spring 4.X 企业应用开发实战》
Java多线程基础-ThreadLocal的更多相关文章
- [转]Java多线程干货系列—(一)Java多线程基础
Java多线程干货系列—(一)Java多线程基础 字数7618 阅读1875 评论21 喜欢86 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们 ...
- Java多线程基础:进程和线程之由来
转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...
- Java 多线程——基础知识
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- Java多线程--基础概念
Java多线程--基础概念 必须知道的几个概念 同步和异步 同步方法一旦开始,调用者必须等到方法调用返回后,才能执行后续行为:而异步方法调用,一旦开始,方法调用就立即返回,调用者不用等待就可以继续执行 ...
- Java多线程基础知识总结
2016-07-18 15:40:51 Java 多线程基础 1. 线程和进程 1.1 进程的概念 进程是表示资源分配的基本单位,又是调度运行的基本单位.例如,用户运行自己的程序,系统就创建一个进程, ...
- Java基础16:Java多线程基础最全总结
Java基础16:Java多线程基础最全总结 Java中的线程 Java之父对线程的定义是: 线程是一个独立执行的调用序列,同一个进程的线程在同一时刻共享一些系统资源(比如文件句柄等)也能访问同一个进 ...
- 1、Java多线程基础:进程和线程之由来
Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...
- Java 多线程基础(一)基本概念
Java 多线程基础(一)基本概念 一.并发与并行 1.并发:指两个或多个事件在同一个时间段内发生. 2.并行:指两个或多个事件在同一时刻发生(同时发生). 在操作系统中,安装了多个程序,并发指的是在 ...
- Java 多线程基础(三) start() 和 run()
Java 多线程基础(三) start() 和 run() 通过之前的学习可以看到,创建多线程过程中,最常用的便是 Thread 类中的 start() 方法和线程类的 run() 方法.两个方法都包 ...
随机推荐
- Unsupervised Feature Learning via Non-Parametric Instance Discrimination
目录 概 主要内容 Wu Z., Xiong Y., Yu S. & Lin D. Unsupervised Feature Learning via Non-Parametric Insta ...
- N-Empress
全排列 基本思想:递归.散列 代码实现 #include<cstdio> const int maxn = 11; int n, P[maxn], hashTable[11] = {fal ...
- Java高级程序设计笔记 • 【第6章 设计模式】
全部章节 >>>> 本章目录 6.1 设计模式 6.1.1 设计模式概述和分类 6.1.2 单列模式介绍 6.1.3 单例模式的实现 6.1.4 实践练习 6.2 单例模式 ...
- Eclipse远程调试Java代码的三种方法
Eclipse远程调试Java代码的三种方法, 第1种方法是用来调试已经启动的Java程序,Eclipse可以随时连接到远程Java程序进行调试, 第2种方法可以调试Java程序启动过程,但是Ecli ...
- Fuchsia OS入门官方文档
Fuchsia Pink + Purple == Fuchsia (a new Operating System) Welcome to Fuchsia! This document has ever ...
- android 解决报错 installation failed with message Failed to finalize session : INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: Failed parse during installPackageLI: Failed to read manifest from /xx/xx/xx.apk
新工程启动报错 怎么办? 解决: 将这个选项去掉勾选后点击ok即可
- yum是什么?repo文件详解,epel简介,yum源的更换,repo和epel区别
yum是什么?repo文件详解,epel简介,yum源的更换,repo和epel区别 简单概括: repo和epel的关系 repo是配置源的,即配置从哪里下载包(以及依赖关系)的. epel是作为桥 ...
- js对象数组多字段排序
来源:js对象数组按照多个字段进行排序 一.数组排序 Array.sort()方法可以传入一个函数作为参数,然后依据该函数的逻辑,进行数组的排序. 一般用法:(数组元素从小大进行排序) var a = ...
- 从如何使用到如何实现一个Promise
前言 这篇文章我们一起来学习如何使用Promise,以及如何实现一个自己的Promise,讲解非常清楚,全程一步一步往后实现,附带详细注释与原理讲解. 如果你觉的这篇文章有帮助到你,️关注+点赞️鼓励 ...
- Cache一致性协议与伪共享问题
Cache一致性协议 在说伪共享问题之前,有必要聊一聊什么是Cache一致性协议 局部性原理 时间局部性:如果一个信息项正在被访问,那么在近期它很可能还会被再次访问 比如循环.方法的反复调用等 空间局 ...