Java线程本地存储ThreadLocal
前言
- ThreadLocal 是一种 无同步 的线程安全实现
- 体现了
Thread-Specific Storage
模式:即使只有一个入口,内部也会为每个线程分配特有的存储空间,线程间 没有共享资源 - 本文将总结
ThreadLocal
的用法与实现细节,希望能帮上忙

ThreadLocal 思维导图

线程安全 示意图
1. 用法
ThreadLocal
的用法很简单, ThreadLocal
提供了下列的public与protected方法,本文将引用 android.os.Looper.java 为例子讲解 ThreadLocal
的用法:

ThradlLocal UML类图
// /frameworks/base/core/java/android/os/Looper.java public class Looper { // ... // sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 设置线程局部变量的值
sThreadLocal.set(new Looper(quitAllowed));
} public static Looper myLooper() {
// 获取线程局部变量的值
return sThreadLocal.get();
} public static void prepare() {
prepare(true);
} // ...
}
ThreadLocal
为 static final变量 ,泛型参数为Looper
,表示接受Looper
类型的值Looper#prepare()
中调用ThreadLocal#set()
设置 变量的值,不同线程设置的值互不干扰,不会相互覆盖Looper#myLooper()
中调用ThreadLocal#get()
获取 变量的值,不同线程获取的值互不干扰

Timethreads图 - 01
子类 重写
ThreadLocal#initialValue()
,可以设置变量的初始值,我们查看相关源码:public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
this.segmentShift = 32 - sshift;//用于高位,判断落在哪个Segment
this.segmentMask = ssize - 1;//用于取模。之前在hashmap的indexFor方法有提过。2的n次方-1
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// create segments and segments[0]
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);//初始化第一个位置的Segment
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];//初始化Segments
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}- 在
ThreadLocal#get()
中尝试获取变量的值,如果为空则会调用ThreadLocal#setInitialValue()
设置设置初始值 - 懒初始化 :直到第一次调用
get()
才会设置初值 - 默认 :设置初始值为 null
- 在
ThreadLocal#remove()
用于 移除 变量之前存储的值。如果在当前线程下次调用ThreadLocal#get()
时,还没有set()
新的值,依旧会使用ThreadLocal#setInitialValue()
设置初始值。
到这里我们就可以总结 ThreadLocal
的生命周期,如下图所示:

ThreadLocal生命周期 示意图
2. 编程规约
记得吗?《阿里巴巴Java开发手册》中提到过关于 ThreadLocal
的编程规约,如下所示:
5.【强制】
SimpleDateFormate
是线程不安全的类,一般不要定义为 static 变量,如果定义为static,必须加锁,或者使用DateUtils
工具类正例:
private static final ThreadLocal<DataFormat> df = new ThreadLocal<DateFormat>(){
@Override
protected DateFormat initialValue(){
return new SimpleDateFormat("yyyy-MM-dd");
}
};说明:如果是JDK8的应用,可以使用
Instant
代替Date
,LocalDateTime
代替Calendar
,DateTimeFormatter
代替SimpleDateFormat
,官方给出的解释:simple beautiful strong immutable thread-safe.15.【参考】(原文过于啰嗦,以下为笔者转述)
ThreadLocal
变量建议使用 static 修饰,可以保证变量在类初始化时创建,所有类实例可以共享同一个静态变量。注意到了吗?在文章开头的Looper.java源码中,
ThreadLocal
变量就是使用static
修饰的
看到这里,相信你已经掌握了 ThreadLocal
的用法,下一篇文章将深入 ThreadLocal
的内部,探讨数据结构的实现细节。
Segment
ConcurrentHashMap是由多个Segment组成的,Segment继承了ReentrantLock,每次加锁都是对某个Segment,不会影响其他Segment,达到了锁分离(也叫分段锁)的作用。
每个Segment又包含了HashEntry数组,HashEntry是一个链表。如下图所示:
concurrencyLevel:并发数,默认16,直接影响segmentShift和segmentMask的值,以及Segment的初始化数量。Segment初始化的数量,为最接近且大于的办等于2的N次方的值,比如concurrencyLevel=16,Segment数量为16,concurrencyLevel=17,Segment数量为32。segmentShift的值是这样的,比如Segment是32,相对于2的5次方,那么他的值就是32-5,为27,后面无符号右移27位,也就是取高5位的时候,就是0到31的值,此时Segment的下标也是0到31,取模后对应着每个Segment。segmentMask就是2的n次方-1,这边n是5,用于取模。之前在hashmap的indexFor方法有提过。
Java线程本地存储ThreadLocal的更多相关文章
- Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic
Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic 1.1. ThreadLocal 设计模式1 1.2. ...
- 线程本地存储 ThreadLocal
线程本地存储 · 语雀 (yuque.com) 线程本地存储提供了线程内存储变量的能力,这些变量是线程私有的. 线程本地存储一般用在跨类.跨方法的传递一些值. 线程本地存储也是解决特定场景下线程安全问 ...
- JAVA线程本地变量ThreadLocal和私有变量的区别
ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些. 所以,在Java中编写线程局部变量的代码相对来说要笨 ...
- ThreadLocal(线程本地存储)
1. ThreadLocal,即线程本地变量或线程本地存储. threadlocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的 ...
- 线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理
原文链接地址:http://www.cppblog.com/Tim/archive/2012/07/04/181018.html 本文为线程本地存储TLS系列之分类和原理. 一.TLS简述和分类 我们 ...
- 线程本地存储(Thread Local Storage, TLS)简单分析与使用
在多线程编程中, 同一个变量, 如果要让多个线程共享访问, 那么这个变量可以使用关键字volatile进行声明; 那么如果一个变量不想使多个线程共享访问, 那么该怎么办呢? 呵呵, 这个办法就是TLS ...
- .NET:线程本地存储、调用上下文、逻辑调用上下文
.NET:线程本地存储.调用上下文.逻辑调用上下文 目录 背景线程本地存储调用上下文逻辑调用上下文备注 背景返回目录 在多线程环境,如果需要将实例的生命周期控制在某个操作的执行期间,该如何设计?经典的 ...
- C# 线程本地存储 调用上下文 逻辑调用上下文
线程本地存储 using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleAppTest ...
- 线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理
本文为线程本地存储TLS系列之分类和原理. 一.TLS简述和分类 我们知道在一个进程中,所有线程是共享同一个地址空间的.所以,如果一个变量是全局的或者是静态的,那么所有线程访问的是同一份,如果某一个线 ...
随机推荐
- KVM系统镜像制作
使用virt-install创建虚拟机并安装GuestOS virt-install是一个命令行工具,它能够为KVM.Xen或其它支持libvirt API的hypervisor创建虚拟机并完成Gue ...
- 第10组 团队Git现场编程实战
组员职责分工 姓名 分工 童景霖 博客 朱晓倩 制作UI 万本琳 制作UI 唐怡 制作UI 陈心怡 制作UI 黄永福 测评福州最受欢迎的商圈.后期代码修改和完善 郑志强 测评各个价位的前五美食餐厅代码 ...
- linux 搭建局域网YUM源仓库服务器
yum简介 Yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及CentOS中的Shell前端软件包管理器.基于RPM包管理,能够从指定的服 ...
- Python10大热门项目
文章地址:https://baijiahao.baidu.com/s?id=1625230403885659615&wfr=spider&for=pc 今天给大家盘点一下实验楼最热门的 ...
- pom.xml activatedProperties --spring.profiles.active=uat 对应
<profiles> <profile> <id>dev</id> <properties> <!-- 环境标识,需要与配置文件的名称 ...
- PowerMock框架讲解及使用
为什么要使用PowerMock 现如今比较流行的Mock工具如jMock .EasyMock .Mockito等都有一个共同的缺点:不能mock静态.final.私有方法等.而PowerMock能够完 ...
- PHP & JS 链接跳转的几种方式
网站开发中,我们经常需要使用链接跳转,比如登录成功后,自动跳转到首页等等,下面方面介绍 PHP & JS 的几种链接跳转方式 PHP <?php header("Locatio ...
- Corrupt JPEG data: 1 extraneous bytes before marker 0xd9 JPEG datastream contains no image
Corrupt JPEG data: 1 extraneous bytes before marker 0xd9 JPEG datastream contains no image 对比发送时的全部数 ...
- angular4.x实现一个全选,反选,外加从下一页返回上一页,选中上一次的操作记录
productMap:any = new Map<string, string>(); //定义一个map的数据模型 //只要操作这个checkbox 那么只管把数据全部勾起了就行了 刷新 ...
- JobStorage.Current property value has not been initialized. You must set it before using Hangfire Client or Server API.
JobStorage.Current property value has not been initialized. You must set it before using Hangfire Cl ...