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简述和分类 我们知道在一个进程中,所有线程是共享同一个地址空间的.所以,如果一个变量是全局的或者是静态的,那么所有线程访问的是同一份,如果某一个线 ...
随机推荐
- DNN的BP算法Python简单实现
BP算法是神经网络的基础,也是最重要的部分.由于误差反向传播的过程中,可能会出现梯度消失或者爆炸,所以需要调整损失函数.在LSTM中,通过sigmoid来实现三个门来解决记忆问题,用tensorflo ...
- 终于明白为什么要加 final 关键字了!
阅读本文大概需要 2.8 分钟. 来源: www.jianshu.com/p/acc8d9a67d0c 在开发过程中,由于习惯的原因,我们可能对某种编程语言的一些特性习以为常,特别是只用一种语言作为日 ...
- JDBC PreparedStatement Statement
参考:预编译语句(Prepared Statements)介绍,以MySQL为例 1. 背景 本文重点讲述MySQL中的预编译语句并从MySQL的Connector/J源码出发讲述其在Java语言中相 ...
- cloneable以及深拷贝和浅拷贝
Objec类有11个方法,有两个protected的方法,其中一个为clone方法(另一个为finalize). 该方法的签名是: protected native Object clone() th ...
- 【转】gdb typeid 详解
在揭开typeid神秘面纱之前,我们先来了解一下RTTI(Run-Time Type Identification,运行时类型识别),它使程序能够获取由基指针或引用所指向的对象的实际派生类型, ...
- springboot框架在容器加载完成之后执行某个方法
问题描述: 想在websocket实现的Handler中执行一些初始化操作,但是初始化操作使用到了@Autowired自动注入的变量,而且是保护类型.第一个想法是放到Handler构造函数中执行,但是 ...
- 美化自己的loading框
美化自己的loading框 ._showloading { position: absolute; left:; width: 100%; height: 100%; top:; background ...
- kotlin基础 枚举
enum class WEEK {星期一,星期二,星期三,星期四,星期五,星期六,星期日} Kotlin 枚举类 枚举类最基本的用法是实现一个类型安全的枚举. 枚举常量用逗号分隔,每个枚举常量都是一个 ...
- (转)Ngx_Lua使用分享
原文:https://www.cnblogs.com/yanzi-meng/p/9450999.html ngx_lua 模块详细讲解(基于openresty)---https://www.cnblo ...
- EasyNVR网页摄像机无插件H5、谷歌Chrome直播方案-Onvif(一)使用Onvif协议进行设备发现以及指定设备信息探测
背景分析 众所周知,EasyNVR不拘泥.不受限于摄像机的品牌厂商及其配套平台,只要是网络监控摄像机IPC.硬盘录像机NVR.且设备支持标准的RTSP/Onvif协议,都可以接入实时视频流进入Easy ...