前言

  • 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的更多相关文章

  1. Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic

    Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic 1.1. ThreadLocal 设计模式1 1.2. ...

  2. 线程本地存储 ThreadLocal

    线程本地存储 · 语雀 (yuque.com) 线程本地存储提供了线程内存储变量的能力,这些变量是线程私有的. 线程本地存储一般用在跨类.跨方法的传递一些值. 线程本地存储也是解决特定场景下线程安全问 ...

  3. JAVA线程本地变量ThreadLocal和私有变量的区别

    ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些. 所以,在Java中编写线程局部变量的代码相对来说要笨 ...

  4. ThreadLocal(线程本地存储)

    1. ThreadLocal,即线程本地变量或线程本地存储. threadlocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的 ...

  5. 线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理

    原文链接地址:http://www.cppblog.com/Tim/archive/2012/07/04/181018.html 本文为线程本地存储TLS系列之分类和原理. 一.TLS简述和分类 我们 ...

  6. 线程本地存储(Thread Local Storage, TLS)简单分析与使用

    在多线程编程中, 同一个变量, 如果要让多个线程共享访问, 那么这个变量可以使用关键字volatile进行声明; 那么如果一个变量不想使多个线程共享访问, 那么该怎么办呢? 呵呵, 这个办法就是TLS ...

  7. .NET:线程本地存储、调用上下文、逻辑调用上下文

    .NET:线程本地存储.调用上下文.逻辑调用上下文 目录 背景线程本地存储调用上下文逻辑调用上下文备注 背景返回目录 在多线程环境,如果需要将实例的生命周期控制在某个操作的执行期间,该如何设计?经典的 ...

  8. C# 线程本地存储 调用上下文 逻辑调用上下文

    线程本地存储 using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleAppTest ...

  9. 线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理

    本文为线程本地存储TLS系列之分类和原理. 一.TLS简述和分类 我们知道在一个进程中,所有线程是共享同一个地址空间的.所以,如果一个变量是全局的或者是静态的,那么所有线程访问的是同一份,如果某一个线 ...

随机推荐

  1. 【BigData】Java基础_数组

    什么是数组?数据是可以装一组数据的变量 1.定义数组 float[] arr1 = new float[10]; // 可以装10个float数据 int[] arr2 = new int[10]; ...

  2. [Web Pdf] flying-saucer + iText + Freemarker生成pdf 跨页问题

    转载于: https://blog.csdn.net/qq_31980421/article/details/79662988 flying-saucer + iText +  Freemarker实 ...

  3. Linux环境下安装python3

    1.安装前准备 CentOS 7 中默认安装了 Python,版本:2.7.5,由于很多基本的命令.软件包都依赖旧版本,比如:yum.所以,在更新 Python 时,建议不要删除旧版本,而且新旧版本可 ...

  4. RocketMQ常用命令【转】

    首先进入 RocketMQ 工程,进入/RocketMQ/bin   在该目录下有个 mqadmin 脚本 . 查看帮助:   在 mqadmin 下可以查看有哪些命令 a: 查看具体命令的使用 :  ...

  5. RSA前台加密后台解密的应用

    写在前面 项目安全测试需要将登录功能修改, AES加密不符合要求, 现改为RSA非对称加密.(将登录密码加密后传给后台, 后台解密后再进行一系列的校验) .期间遇到了前台js加密但是后台解密失败的问题 ...

  6. eclipse从git下载的maven项目需要转成maven才可是使用main方法启动

    导入git项目: 选择导入git项目有会有两个选项:一个是从本地git仓库中导入项目,一个是从github远程仓库中导入项目 我们选择从远程仓库中导入项目: 然后选择本地存放该项目的git仓库 然后选 ...

  7. 【GMT43智能液晶模块】例程二十二:USB_CDC实验——高速数据传输

    源代码下载链接: 链接:https://pan.baidu.com/s/10KOWONWbNYlonyuX0W0Mcg 提取码:ggpo 复制这段内容后打开百度网盘手机App,操作更方便哦 GMT43 ...

  8. asp.net msbuild 发布

    "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\msbuild.exe ...

  9. dotnet core 3.0 swagger 显示枚举描述

    上一篇net core 2.2 swagger的枚举描述,core 3.0 需要升级swagger到5.0rc版,配置需要做些修改,swaager启用了OpenApi标准,之前的枚举描述方法也失效了. ...

  10. python字符串拼接N种姿势

    字符串大家都不陌生,应用比较广泛,强大,总是会给你一些惊喜的数据类型.我们本篇文章主要介绍的就是关于字符串的多种方法的拼接. 第一种:直接通过+号拼接 输出结果: 2.通过 str.join()方法拼 ...