100%保证线程安全,还有这种黑科技?

简介: 大家好,我是指北君。今天学习了ThreadLocal相关的知识,发现原来道哥(Doug Lea)也用ThreadLocal。既然大师们都喜欢用的,我们必须得研究起来。大师的背影总是需要追随。

那么指北君给大家安排上了,如果你拥有了Java中的ThreadLocal,那麽你可以创建一个只允许同一个线程读写的变量。因此,即使两个线程执行了相同的代码,并且引用了相同的ThreadLocal变量,这两个线程也无法看到彼此的ThreadLocal。可以说ThreadLocal提供了一种代码线程安全的的简单方法。

下面我们就来看看道哥都用的ThreadLocal。

1 ThreadLocal你来自哪里

60.png

又是并发大佬们的杰作,膜拜一下。怪不得道哥也爱用,自己设计的类总得用用。下面来看看基本内容与用法吧。


61.jpg


2 ThreadLocal原理


首先请看男神们的介绍

“This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).”

“此类提供了thread-local变量。这些变量不同于普通的类似变量,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自有的,独立初始化的变量副本,ThreadLocal实例通常是希望将状态与线程(例如,用户ID或事务ID)关联的类中的私有静态字段。”

通过老爷子们的描述,指北君大概也知道了ThreadLocal的推荐使用场景,

  1. ThreadLocal提供了一种访问某个特有变量的方法 访问到的变量属于当前线程,同一线程在任何地方都能访问同一个线程特有变量。
  2. 推荐定义为 private static 类型,但是Doug Lea老爷子在ThreadLocalRandom 和 ReentrantReadWriteLock 中使用了 private static final 类型。(肯定是当年写简介的时候手抖了)


2.1 Thread中如何存储


既然是线程的变量,自然是存在Thread对象中的一个变量了,但是它是通过ThreadLocal这个类来维护的。



62.png

ThreadLocal中有一个内部类来ThreadLocalMap来维护这些线程本地变量,

63.png


ThreadLocalMap中的Entry结构如下,是一种key为弱引用(其目的就是Entry对象在GC时容易回收)的hash map,其中key总是ThreadLocal。

64.png


2.2 常用方法 get,set,remove 详解


  • get() 此方法是ThreadLocal最重要的方法之一,该方法返回此线程局部变量的当前线程副本中的值。大概可分为以下几步:
    (1) 先获取当前线程,然后再从线程中得到ThreadLocalMap。
    (2) 然后使用ThreadLocal对象的threadLocalHashCode进行散列计算,得到一个数组的index
    (3) 从Table数组中得到Entry,再对比Entry的key是不是和当前的ThreadLocal相等,如果相等就返回此Entry的value
    (4) 如果上一步中得到的Entry与当前ThreadLocal不相等,则会在方法getEntryAfterMiss中进行遍历Entry数组table中的每一个元素,如果找不到就返回null。而且在遍历的过程中会顺便清理一下废弃的Entry。

下面可以看一下get方法的具体代码。

65.png66.png

  • set(T value) 此方法将此线程局部变量的当前线程副本中的值设置为指定值。

set线程本地变量步骤如下:

(1) 首先依然是获取此线程的ThreadLocalMap

(2) Map不为null时往map中插入数据,否侧创建map并插入数据

(3) 具体的set方法依然是先遍历Entry数组中所有的的Entry,然后依次对比每个Entry的key是否等于当前ThreadLocal,如果相等则直接替换现有Entry的value。如果Entry的Key为null,则立马清理废弃的Entry,并用新的Entry来替换此卡槽。

(4) 如果遍历完都没有return,则在在table中相应卡槽下新建Entry对象

67.png68.png

remove() remove则相对简单,直接遍历ThreadLocalMap中Entry数组table,找到对应的Entry,将Entry的key置为null,然后再清理相应的Entry。


69.png


3 Java中使用的ThreadLocal


Java中有哪些源码使用了ThreadLocal。

ThreadLocalRandom 中使用计算nextGaussian值时有使用到ThreadLocal。

InheritableThreadLocal继承了ThreadLocal,线程中使用inheritableThreadLocals这个map存储线程本地变量。和ThreadLocal的区别就是子线程依然可以访问到父线程的线程本地变量,实际应用中也推荐InheritableThreadLocal

ReentrantReadWriteLock中线程读写锁的计数器使用了ThreadLocal,其目的是记录每个线程获取读写锁的次数



70.png


4 如何使用ThreadLocal


ThreadLocal非常适合存储非线程安全的对象,并且不需要跨线程共享对象。很多需要线程隔离的操作都可以尝试使用它。

ThreadLocal也非常适合在Web应用程序中使用,典型的应用就是在Web请求进来一开始就将请求状态存储在ThreadLocal中,然后参与处理的任何组件均可访问该状态。

以下是一个ThreadLocal示例:

具体使用就是配合interceptor或者filter在线程刚开始执行的时候存储SessionContext,线程执行过程中可以随时访问该变量。然后在线程执行结束的时候再调用remove()方法移除,防止内存泄漏。


image.png

总结

本文介绍了ThreadLocal的原理以及解析了常用方法的实现逻辑,以及在ThreadLocal一些应用。在一步步梳理的过程中,果然看到了以往忽略的各种细节,最后给出了一个小Case。并发编程大神道哥.李都在用的ThreadLocal,不妨在自己的项目中偷偷用上,保证丝滑舒适。

我是指北君,操千曲而后晓声,观千剑而后识器。感谢各位人才的:点赞、收藏和评论,我们下期更精彩!




相关文章
|
5月前
|
存储 监控 安全
解锁ThreadLocal的问题集:如何规避多线程中的坑
解锁ThreadLocal的问题集:如何规避多线程中的坑
168 0
|
存储 安全 Java
到底如何保证线程安全,总结得太好了。。
一、线程安全等级 之前的博客中已有所提及“线程安全”问题,一般我们常说某某类是线程安全的,某某是非线程安全的。其实线程安全并不是一个“非黑即白”单项选择题。
965 0
到底如何保证线程安全,总结得太好了。。
|
5月前
|
消息中间件 前端开发 NoSQL
腾讯面试:什么锁比读写锁性能更高?
在并发编程中,读写锁 ReentrantReadWriteLock 的性能已经算是比较高的了,因为它将悲观锁的粒度分的更细,在它里面有读锁和写锁,当所有操作为读操作时,并发线程是可以共享读锁同时运行的,这样就无需排队执行了,所以执行效率也就更高。 那么问题来了,有没有比读写锁 ReentrantReadWriteLock 性能更高的锁呢? 答案是有的,在 Java 中,比 ReentrantReadWriteLock 性能更高的锁有以下两种: 1. **乐观锁**:乐观锁是一种非阻塞锁机制,它是通过 Compare-And-Swap(CAS)对比并替换来进行数据的更改的,它假设多个线程(
50 2
|
2月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
52 1
|
3月前
|
算法 Java 编译器
多线程线程安全问题之系统层面的锁优化有哪些常见的策略
多线程线程安全问题之系统层面的锁优化有哪些常见的策略
|
2月前
|
存储 缓存 安全
聊一聊高效并发之线程安全
该文章主要探讨了高效并发中的线程安全问题,包括线程安全的定义、线程安全的类别划分以及实现线程安全的一些方法。
|
3月前
|
存储 设计模式 监控
Java面试题:如何在不牺牲性能的前提下,实现一个线程安全的单例模式?如何在生产者-消费者模式中平衡生产和消费的速度?Java内存模型规定了变量在内存中的存储和线程间的交互规则
Java面试题:如何在不牺牲性能的前提下,实现一个线程安全的单例模式?如何在生产者-消费者模式中平衡生产和消费的速度?Java内存模型规定了变量在内存中的存储和线程间的交互规则
38 0
|
4月前
|
缓存 并行计算 安全
【并发编程系列一】并发编年史:线程的双刃剑——从优势到风险的全面解析
【并发编程系列一】并发编年史:线程的双刃剑——从优势到风险的全面解析
|
12月前
|
安全 Java
Java多线程编程中的线程安全与最佳实践
Java多线程编程中的线程安全与最佳实践
90 0
|
存储 人工智能 移动开发
【Java基础】解锁多线程安全的秘密武器:探索ThreadLocal的神奇力量!
ThreadLocal是Java中的一个类,用于在多线程环境下,为**每个线程**提供**独立的变量副本**。每个线程都可以独立地访问自己的变量副本,因为该独立变量属于**当前线程**,对其他线程而言是隔离的,不会与其他线程的副本产生冲突。