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() 方法.两个方法都包 ...
随机推荐
- [opencv]三通道图像反色
1.用纯白图像-原图 Mat img = imread(path); imshow("src", img); waitKey(); Mat white = cv::Mat(250, ...
- x86-1-32位x86 处理器编程架构
x86(32位)-1-32位x86 处理器编程架构 Intel 32 位处理器架构简称IA-32(Intel Architecture,32-bit) x86是指intel的86系列的CPU统称,比如 ...
- ASP.NET+MVC入门踩坑笔记 (一) 创建项目 项目配置运行 以及简单的Api搭建
哈喽各位 我又回来了! 前段时间研究了下ASP.NET,刚开始也是随便找网上的各种教程来看,但是鉴于本人技术有限,还是走了相当长的一段弯路的.所以我写下了这篇文章.希望各位刚刚入坑的ASP.NET开发 ...
- 初识python:time 模版
语法及示例代码如下: import time # time 时间戳,1970年到当前时间的秒数 print('time:',time.time()) # sleep 延时.睡眠(s) print('s ...
- spring boot + thymeleaf +security自定义规则 的简单使用
1.前言 以前开发一直使用 springMVC模式开发 ,前端页面常使用 JSP ,现在html5淘汰了 ,要么使用html ,要么使用vue , 现在使用spring boot ,有必要总结一下 ...
- java邮件打包在linux备份数据库练习
注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6812982512256549387/ 承接上一篇文档<Java实现163邮箱发送邮件到QQ邮箱> 主方 ...
- CTF-sql-宽字节注入
本文章主要涉及sql宽字节注入注入的原理讲解,如有错误,望指出.(附有目录,如需查看请点右下角) 一.首先介绍一下本篇文章所用到的知识点: 常用到的url编码: 空格:%20 单引号:%27 在sql ...
- 《剑指offer》面试题18. 删除链表的节点
问题描述 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点. 返回删除后的链表的头节点. 注意:此题对比原题有改动 示例 1: 输入: head = [4,5,1,9], val = ...
- 【SpringCloud-Alibaba】环境搭建以及注意事项
一.开发环境 JDK 1.8 SpringBoot 2.1.7.RELEASE SpringCloud-Alibaba 2.1.2.RELEASE 数据库MySQL 5.8 如果需要修改版本请参照:S ...
- 源代码管理git地址从http改为https,提交400错误
推送400错误 cmd 执行 git config --global http.sslVerify false 推送地址,修改http 为 https 就可以正常提交了