前言

  • 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. 一起学Makefile(三)

    makefile工具箱 complicated项目的构建 文件结构如下: 文件内容如下: 项目依赖关系: gcc编译出可执行文件的过程包含了两个过程,编译和链接. makefile如下: 运行结果:

  2. About me recently

    About me recently Recently I fell that memory has always been problematic.Maybe I hava bee too tired ...

  3. nginx 访问控制之deny allow

    Nginx的deny和allow指令是由ngx_http_access_module模块提供,Nginx安装默认内置了该模块. 除非在安装时有指定 --without-http_access_modu ...

  4. 代码注入/文件包含 弹出Meterpreter

    主要通过 msf 中 exploit 的 web_delivery 模块来实现此功能 0x01 前提背景 目标设备存在远程文件包含漏洞或者命令注入漏洞,想在目标设备上加载webshell,但不想在目标 ...

  5. JS 根据不同年份,获取不同月份的天数

    在做日期方面的需求时,需要考虑到大小月的问题  ##格式:new Date(year,month,0).getDate() new Date(2019,12,0).getDate() 可以在控制台打印 ...

  6. vue-cli安装webpack项目及初始配置

    这个下载包是自己基于 webpack 搞的,可以看看https://github.com/chichengyu/webpack vue-cli安装 输入 npm install vue-cli -g ...

  7. 异常值检测方法(Z-score,DBSCAN,孤立森林)

     机器学习_深度学习_入门经典(博主永久免费教学视频系列) https://study.163.com/course/courseMain.htm?courseId=1006390023&sh ...

  8. Annotation-based argument resolution 部分2

    HandlerMethodArgumentResolver的抽象實現AbstractNamedValueMethodArgumentResolver下的子类  部分1 RequestParamMapM ...

  9. hangfire 实现已完成的job设置过期,防止数据无限增长

    1.添加SucceededStateExpireHandler 继承接口 IStateHandler public class SucceededStateExpireHandler : IState ...

  10. Java:JSON和Map相互转换

    Java:JSON与Map相互转换 需要jar包:jackson-core-2.6.0.jar,jackson-databind-2.6.0.jar,jackson-annotations-2.6.0 ...