Java 并发编程:ThreadLocal 的使用及其源码实现
1、ThreadLocal的使用
防止任务在共享资源上产生冲突的一种方式是根除对变量的共享,使用线程的本地存储为使用相同变量的不同线程创建不同的存储。
下面是一个 ThreadLocal 的实例。这里我们使用了静态的全局变量 ThreadLocal
对象来保存 Integer
类型的值。我们在不同的线程中将指定的数字传入到 threadLocal
中进行保存。然后,再将其读取出来:
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();public static void main(String...args) {threadLocal.set(-1);ExecutorService executor = Executors.newCachedThreadPool();for (int i=0; i<5; i++) {final int ii = i; // i不能是final的,创建临时变量executor.submit(new Runnable() {public void run() {threadLocal.set(ii);System.out.println(threadLocal.get());}});}executor.shutdown();System.out.println(threadLocal.get());}
复制代码
从程序的执行结果可以看出,每个线程都正确地读取出来了保存到 ThreadLocal 中的数据。
所以,我们总结一下 ThreadLocal
的作用就是,存储在 ThreadLocal
中的变量是线程安全的,每个线程只能读取出自己存储的值。
通常它的使用方式就是定义一个静态全局的 ThreadLocal
实例,然后每个线程使用它来读写只有自己会用到的数据。比如,我们要为每个线程创建了一个数据库连接,并且该连接只允许该线程自己使用,那么可以将它存储在 ThreadLocal
中,然后在用到的地方获取。
看了上面的例子,也许你会又以下一些问题:
- ThreadLocal中存储的值是如何保证绝对的线程安全的?
- 那么这些值是存储在什么地方?
- 是静态类型的还是实例类型的?
- 如果某个线程执行完毕了,被销毁了,那么这些存储的值会被怎么处理呢?
- ……
带着上面的这些问题,我们来看下在JDK源码中 ThreadLocal
是如何实现的。
2、ThreadLocal的作用原理
我们还是先从读取的操作来看。
以下是 ThreadLocal
中 set()
方法的代码:
public T get() {Thread t = Thread.currentThread(); // 1ThreadLocalMap map = getMap(t); // 2if (map != null) { // 3ThreadLocalMap.Entry e = map.getEntry(this); // 4if (e != null) {T result = (T) e.value; // 5return result;}}return setInitialValue();}
复制代码
这里首先会再步骤1中获取到当前线程的实例,然后在步骤2中通过getMap()
方法,使用当前的线程的ThreadLocalMap
。这里的ThreadLocalMap
的定义如下:
static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}private Entry[] table;private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);}// ...}
复制代码
然后,我们看下getMap()
方法的定义:
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}
复制代码
也就是说实际上当我们调用 get()
方法的时候,会先获取当前线程中的 threadLocals
字段,该字段是 ThreadLocalMap
类型的。然后,我们使用当前的 ThreadLocal
实例作为键来从哈希表中获取到一个 Entry
,而实际的值就保存再 Entry
的 value
字段中。
就像上面的 getEntry()
方法定义的那样,似乎这里的哈希表只是一个数组,那哈希冲突是怎么解决的呢?实际上,我们知道通常解决哈希冲突有两种解决方式,一种是拉链法,一种是线性探测法。前者在 HashMap
和 ConcurrentHashMap
中使用较多,而这里用到的其实就是线性探测法。说白了就是将所有的值放在一个数组里面然后根据散列的结果到数组中取值,具体的实现方式可以看相关的数据结构知识点。
这里的关系是不是有点乱,我们来捋一下:
我们使用ThreadLocal
存储的值实际是存储在Thread
使用ThreadLocalMap
当中的,而这里的ThreadLocal
实例值起到了一个哈希表的键的作用:
就像上图显示的那样,假如我们在线程thread1
中调用了threadLocal1
的get()
方法,首先会用Thread.currentThread()
方法获取到thread1
,然后获取到thread1
的threadLocals
实例,threadLocals
是一个ThreadLocalMap
类型的哈希表。然后,我们再用threadLocal1
作为键来从threadLocals
中获取到值Entry
,并从Entry
中取出存储的值并返回。
至此,我们已经了解了ThreadLocal的实现的原理,本来想看下set()
方法的,但是到此已经基本真相大白了,所以也就没有继续下去的必要了。
3、总结
我们回过头来看下之前提出的几个问题:
- ThreadLocal中存储的值是如何保证绝对的线程安全的? 实际上每个值都是存在线程内部的,ThreadLocal只用来帮助我们从该线程内部的哈希表中找到存放的那个值。
- 那么这些值是存储在什么地方?线程内部的实例字段。
- 是静态类型的还是实例类型的?线程内部的实例字段。
- 如果某个线程执行完毕了,被销毁了,那么这些存储的值会被怎么处理呢?因为是线程的局部字段,所以线程不在了,值就没有了。
以上就是ThreadLocal的用法和实现原理。
Java 并发编程:ThreadLocal 的使用及其源码实现相关推荐
- Java并发编程(十六):CyclicBarrier源码分析
前言 CyclicBarrier可以建立一个屏障,这个屏障可以阻塞一个线程直到指定的所有线程都达到屏障.就像团队聚餐,等所有人都到齐了再一起动筷子.根据Cyclic就可以发现CyclicBarri ...
- Java并发编程—ThreadLocal底层原理
作者:Java3y 链接:https://www.zhihu.com/question/341005993/answer/793627819 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权, ...
- Java并发编程—ThreadLocal用法详解
一.用法 ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量. Threa ...
- Java 并发编程Semaphore的应用与源码解析
What Semaphore标识信号量,允许指定数量的线程同时访问某个资源 How 通过以下两部实现信号量: acquire方法用于获得准入许可(如果没有获得许可,则进行等待,直到有线程释放许可而获得 ...
- Java 并发编程CyclicBarrier的应用与源码解析(基于ReentrantLock实现)
什么是CyclicBarrier? CyclicBarrie和上一篇中讲到CountDownLatch很类似,它能阻塞一组线程直到某个事件的发生. 栅栏与闭锁的关键区别在于:所有必须同时到达栅栏位置才 ...
- 【Java并发编程】16、ReentrantReadWriteLock源码分析
一.前言 在分析了锁框架的其他类之后,下面进入锁框架中最后一个类ReentrantReadWriteLock的分析,它表示可重入读写锁,ReentrantReadWriteLock中包含了两种锁,读锁 ...
- Java并发编程与技术内幕:ConcurrentHashMap源码解析
林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文主要讲了Java中ConcurrentHashMap 的源码 ConcurrentH ...
- Java并发编程笔记之Semaphore信号量源码分析
JUC 中 Semaphore 的使用与原理分析,Semaphore 也是 Java 中的一个同步器,与 CountDownLatch 和 CycleBarrier 不同在于它内部的计数器是递增的,那 ...
- Java 并发编程CountDownLatch的应用与源码解析
应用场景 CountDownLatch是一个多线程控制工具.用来控制线程的等待. 设置需要countDown的数量,然后每一个线程执行完毕后调用countDown()方法,而在主线程中调用await( ...
- java并发编程基础-ReentrantLock及LinkedBlockingQueue源码分析
ReentrantLock是一个较为常用的锁对象.在上次分析的uil开源项目中也多次被用到,下面谈谈其概念和基本使用. 概念 一个可重入的互斥锁定 Lock,它具有与使用 synchronized 相 ...
最新文章
- 解决报错:tensorflow.python.framework.errors_impl.UnknownError: Failed to get convolution algorithm.
- Linux关于终端的基本概念汇总(tty/pty)(转)
- Storm WordCount
- 太相信书的人,格局不会太大
- 解决springboot配置jackson.date-format不生效的问题
- 敏捷软件开发 12 原则
- pulseaudio数据流框图
- 【渝粤教育】电大中专电商运营实操 (17)作业 题库
- EXCEL中汉字转变拼音的技巧~代码实现
- java 数独游戏_java数独游戏完整版分享
- macOS 上编译 Dynamips
- 纺织服装产业实现智能制造升级
- 如何使用音频转换器将多个音频合并为一个音频
- MFC添加afx_msg点击事件
- 《娱乐至死》读书笔记
- Ridge Regression and Kernel Ridge Regression
- 用另一种方式解决机房管理助手!(非结束进程版)
- 商品cta策略_《衍生品系列研究之三》:国内商品期货常用日内CTA策略测试
- DRDOS,Novell DRDOS,Calder DRDOS, Real32 DOS 多用户多任务操作系统使用心得
- 墨天轮访谈 | 华为云温云博:从客户视角出发,GaussDB(for Redis)究竟“香”在哪里?