当前线程的值传递,ThreadLocal

通过ThreadLocal设值,在线程内可获取,即时获取值时在其它Class或其它Method。

  1. public class BasicUsage {
  2. private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
  3. public static void main(String[] args) {
  4. threadLocal.set(1);
  5. otherMethod();
  6. }
  7. public static void otherMethod() {
  8. System.out.println("threadLocal.get() -> " + threadLocal.get()); // 其它Class、其它方法,只要在此线程内就可获取
  9. }
  10. }

结果:threadLocal.get() -> 1

如何设置值

这是设置值的方法,关键在于我们将值存放于ThreadLocalMap中

  1. public void set(T value) {
  2. Thread t = Thread.currentThread(); // 获取当前线程
  3. ThreadLocalMap map = getMap(t); // 获取ThreadLocalMap
  4. if (map != null)
  5. map.set(this, value); // 将值设置进ThreadLocalMap中
  6. else
  7. createMap(t, value); // 创建ThreadLocalMap
  8. }

跟踪进去getMap(t),可知ThreadLocalMap是声明在Thread中的threadLocals变量中

  1. ThreadLocalMap getMap(Thread t) {
  2. return t.threadLocals;
  3. }

跟踪进去createMap(t, value),可以看到实例化ThreadLocalMap的代码,我们留意到第一个参数是this,也就是ThreadLocal对象,下文会有提到:

  1. void createMap(Thread t, T firstValue) {
  2. t.threadLocals = new ThreadLocalMap(this, firstValue);
  3. }

跟踪进ThreadLocalMap的构造方法,可以发现它内部维护一个Entry数组,构造方法的代码基本围绕将这个值应存放于数组的哪个下标。这里请注意下实例化Entry的参数,继续跟踪Entry:

  1. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  2. table = new Entry[INITIAL_CAPACITY]; // 初始化初始长度的Entry数组
  3. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 与数组长度做与运算,得到存放的下标
  4. table[i] = new Entry(firstKey, firstValue); // 实例化一个Entry对象
  5. size = 1; // 记录当前的大小
  6. setThreshold(INITIAL_CAPACITY); // 设置阀值,未以后扩容作计算依据
  7. }

跟踪进Entry的构造方法,发现Entry继承WeakReference,以ThreadLocal作为Key,自己存储value:

  1. static class Entry extends WeakReference<ThreadLocal<?>> {
  2. /** The value associated with this ThreadLocal. */ // 此值与threadLocal关联
  3. Object value;
  4. Entry(ThreadLocal<?> k, Object v) {
  5. super(k);
  6. value = v;
  7. }
  8. }

整体结构

  1. 值保持在Thread中的变量threadLocals,此变量类型为ThreadLocal.ThreadLocalMap,可以看到,是ThreadLocal的内部类
  2. ThreadLocal.ThreadLocalMap内部维护一个数组变量Entry[] table,这个数组类型是Entry
  3. Entry类型继承WeakReference<ThreadLocal<?>>Entry会维护两个属性,分别是,其中由其自身维护,见Object valueWeakReference维护,可知如果不存在引用指向对象可能被GC回收
  4. 数据以Entry的格式存储在ThreadLocal.ThreadLocalMap的变量Entry[] table中,其中位置这么计算int i = key.threadLocalHashCode & (len-1)

父线程、子线程中的值传递

JDK的InheritableThreadLocal

使用ThreadLocal中如果使用多线程,会发现父线程设置的值在子线程中无法获取,JDK中有InheritableThreadLocal解决此问题。

  1. public class SubThreadUsage {
  2. private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();
  3. public static void main(String[] args) {
  4. threadLocal.set(1);
  5. // 新启一个线程
  6. new Thread(new Runnable() {
  7. @Override
  8. public void run() {
  9. otherMethod();
  10. }
  11. }).start();
  12. }
  13. public static void otherMethod() {
  14. System.out.println("threadLocal.get() -> " + threadLocal.get());
  15. }
  16. }

结果:threadLocal.get() -> 1

原理简述:

  1. InheritableThreadLocal,会发现其继承ThreadLocal<T>,并且数据存放在Thread的变量inheritableThreadLocals中,变量类型是ThreadLocal.ThreadLocalMap
  2. Thread构造方法调用的init()中,可看见如果parent.inheritableThreadLocals不为空,则ThreadLocal.createInheritedMap()拷贝ThreadLocalMap,拷贝实际调用的是构造方法ThreadLocalMap(ThreadLocalMap),为浅拷贝

所以,如果运用线程池等线程复用技术,传递的数据会有遗留:

  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. import java.util.concurrent.TimeUnit;
  4. public class SubThreadReuseThreadUsage {
  5. private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();
  6. public static void main(String[] args) throws InterruptedException {
  7. threadLocal.set(1);
  8. /* 声明多线程组件 */
  9. ExecutorService executorService = Executors.newSingleThreadExecutor();
  10. Runnable runnableA = new Runnable() {
  11. @Override
  12. public void run() {
  13. otherMethodA();
  14. }
  15. };
  16. Runnable runnableB = new Runnable() {
  17. @Override
  18. public void run() {
  19. otherMethodB();
  20. }
  21. };
  22. // 运行一个线程
  23. executorService.execute(runnableA);
  24. TimeUnit.SECONDS.sleep(1); // 睡眠,让上面线程跑完
  25. /* 运行一个线程 */
  26. executorService.execute(runnableB);
  27. }
  28. public static void otherMethodA() {
  29. System.out.println("threadLocal.get() -> " + threadLocal.get());
  30. threadLocal.set(2);
  31. }
  32. public static void otherMethodB() {
  33. System.out.println("threadLocal.get() -> " + threadLocal.get());
  34. }
  35. }

结果:

  1. threadLocal.get() -> 1
  2. threadLocal.get() -> 2

TransmittableThreadLocal

而线程复用技术因减低线程开销而常用,所以需解决此问题,阿里开源的TransmittableThreadLocal是一个方案,其实现加强了InheritableThreadLocal

用TransmittableThreadLocal、TtlRunnable的简单例子:

  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. import java.util.concurrent.TimeUnit;
  4. import com.alibaba.ttl.TransmittableThreadLocal;
  5. import com.alibaba.ttl.TtlRunnable;
  6. public class SubThreadReuseThreadUsage {
  7. private static ThreadLocal<Integer> threadLocal = new TransmittableThreadLocal<Integer>();
  8. public static void main(String[] args) throws InterruptedException {
  9. threadLocal.set(1);
  10. /* 声明多线程组件 */
  11. ExecutorService executorService = Executors.newSingleThreadExecutor();
  12. Runnable runnableA = new Runnable() {
  13. @Override
  14. public void run() {
  15. otherMethodA();
  16. }
  17. };
  18. Runnable runnableB = new Runnable() {
  19. @Override
  20. public void run() {
  21. otherMethodB();
  22. }
  23. };
  24. TtlRunnable ttlRunnableA = TtlRunnable.get(runnableA);
  25. TtlRunnable ttlRunnableB = TtlRunnable.get(runnableB);
  26. // 运行一个线程
  27. executorService.execute(ttlRunnableA);
  28. TimeUnit.SECONDS.sleep(1); // 睡眠,让上面线程跑完
  29. /* 运行一个线程 */
  30. executorService.execute(ttlRunnableB);
  31. }
  32. public static void otherMethodA() {
  33. System.out.println("threadLocal.get() -> " + threadLocal.get());
  34. threadLocal.set(2);
  35. }
  36. public static void otherMethodB() {
  37. System.out.println("threadLocal.get() -> " + threadLocal.get());
  38. }
  39. }

结果:

  1. threadLocal.get() -> 1
  2. threadLocal.get() -> 1

这里通过使用TtlRunnableTtlCallable完成,还可以通过使用TtlExecutors完成,另外还有无侵入方案Java Agent,详情见此

【原理】Java的ThreadLocal实现原理浅读的更多相关文章

  1. Java集合&Spring源码浅读

    记录自己现在知道的,以后了解了更多的话,再继续补上来 Java集合类 Collection 接口 说明:是List,set 的父类.定义了集合初始模样.集合只存储对象. Jdk8文档,内部方法定义有: ...

  2. Java中ThreadLocal无锁化线程封闭实现原理

    虽然现在可以说很多程序员会用ThreadLocal,但是我相信大多数程序员还不知道ThreadLocal,而使用ThreadLocal的程序员大多只是知道其然而不知其所以然,因此,使用ThreadLo ...

  3. Java多线程——ThreadLocal类的原理和使用

    Java多线程——ThreadLocal类的原理和使用 摘要:本文主要学习了ThreadLocal类的原理和使用. 概述 是什么 ThreadLocal可以用来维护一个变量,提供了一个ThreadLo ...

  4. java.lang.ThreadLocal的作用和原理?列举在哪些程序中见过ThreadLocal的使用?

    java.lang.ThreadLocal的作用和原理?列举在哪些程序中见过ThreadLocal的使用? 说明类java.lang.ThreadLocal的作用和原理.列举在哪些程序中见过Threa ...

  5. [Java多线程]-ThreadLocal源码及原理的深入分析

    ThreadLocal<T>类:以空间换时间提供一种多线程更快捷访问变量的方式.这种方式不存在竞争,所以也不存在并发的安全性问题. //-------------------------- ...

  6. 原子类java.util.concurrent.atomic.*原理分析

    原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...

  7. java的nio之:java的nio的原理

    转载:http://weixiaolu.iteye.com/blog/1479656 Java NIO原理图文分析及代码实现 前言: 最近在分析hadoop的RPC(Remote Procedure ...

  8. Java NIO使用及原理分析 (四)

    在上一篇文章中介绍了关于缓冲区的一些细节内容,现在终于可以进入NIO中最有意思的部分非阻塞I/O.通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据.同样,写入调用将会阻塞直至 ...

  9. Java位向量的实现原理与巧妙应用

    Java位向量的巧实现原理与巧妙应用 1.博文介绍 本篇博文将会介绍几本的位运算含义.位向量介绍.BitSet实现原理.Java位向量的应用.拓展介绍Bloom Filter等. 2.位运算介绍 1) ...

随机推荐

  1. ios中网络请求缓存

    #import <Foundation/Foundation.h> #import "ASIFormDataRequest.h" @protocol NetWorkde ...

  2. [转载][Groovy] Groovy与Java的区别(一)

    原文地址:[Groovy] Groovy与Java的区别(一)作者:langyizhao 因为Groovy可以用Java的所有功能(虽然JVM不同的时候可能会比较麻烦,比如在Android上),所以G ...

  3. iOS获取真机沙盒文件、获取真机本地数据

    有时我们需要对真机内的数据进行分析,那么如何获取沙盒所有数据文件呢? 1.设备连接到电脑,打开xcode 2.打开window-devices 3.打开后,选择设备名,选择app,导出数据 4.最后拿 ...

  4. Swift3 隐藏状态栏,修改状态栏颜色

    之前做法: override func viewWillAppear(_ animated: Bool) { UIApplication.shared.isStatusBarHidden = true ...

  5. mysql8.0 Authentication plugin 'caching_sha2_password' cannot be loaded

    安装mysql8.0后使用navicat创建连接, 然后报如题所示警告.可参考如下解决方案: https://stackoverflow.com/questions/49194719/authenti ...

  6. iOS 播放远程网络音乐的核心技术点

    一.前言 这两天做了个小项目涉及到了远程音乐播放,因为第一次做这种音乐项目,边查资料边做,其中涉及到主要技术点有: 如何播放远程网络音乐 如何切换当前正在播放中的音乐资源 如何监听音乐播放的各种状态( ...

  7. 【DeepLearning】Exercise:Learning color features with Sparse Autoencoders

    Exercise:Learning color features with Sparse Autoencoders 习题链接:Exercise:Learning color features with ...

  8. 【转】25.windbg-!gle、g(错误码、g系列)

    !gle !gle 扩展显示当前线程的最后一个错误码.这个太好记了,getlasterror取首字母: <span style=:> !gle LastErrorValue: (Win32 ...

  9. 【Struts2】Struts2框架的搭建

    1,Struts2简介 struts1和struts2都是由Apache组织发布的,但是比较有趣的是struts2和struts1并没有“血缘关系”.在Apache发布struts1之后,当时是还是非 ...

  10. 【javascript】escape()、encodeURI()、encodeURIComponent()区别详解

    JavaScript中有三个可以对字符串编码的函数,分别是: escape,encodeURI,encodeURIComponent,相应3个解码函数:unescape,decodeURI,decod ...