博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【原理】Java的ThreadLocal实现原理浅读
阅读量:5914 次
发布时间:2019-06-19

本文共 6359 字,大约阅读时间需要 21 分钟。

当前线程的值传递,ThreadLocal

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

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

结果:threadLocal.get() -> 1

如何设置值

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

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

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

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

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

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

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

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

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

static class Entry extends WeakReference
> { /** The value associated with this ThreadLocal. */ // 此值与threadLocal关联 Object value; Entry(ThreadLocal
k, Object v) { super(k); value = v; } }

整体结构

  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)

463931-20170527225702782-746329247.png

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

JDK的InheritableThreadLocal

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

public class SubThreadUsage {        private static ThreadLocal
threadLocal = new InheritableThreadLocal
(); public static void main(String[] args) { threadLocal.set(1); // 新启一个线程 new Thread(new Runnable() { @Override public void run() { otherMethod(); } }).start(); } public static void otherMethod() { System.out.println("threadLocal.get() -> " + threadLocal.get()); }}

结果:threadLocal.get() -> 1

原理简述:

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

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

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;public class SubThreadReuseThreadUsage {        private static ThreadLocal
threadLocal = new InheritableThreadLocal
(); public static void main(String[] args) throws InterruptedException { threadLocal.set(1); /* 声明多线程组件 */ ExecutorService executorService = Executors.newSingleThreadExecutor(); Runnable runnableA = new Runnable() { @Override public void run() { otherMethodA(); } }; Runnable runnableB = new Runnable() { @Override public void run() { otherMethodB(); } }; // 运行一个线程 executorService.execute(runnableA); TimeUnit.SECONDS.sleep(1); // 睡眠,让上面线程跑完 /* 运行一个线程 */ executorService.execute(runnableB); } public static void otherMethodA() { System.out.println("threadLocal.get() -> " + threadLocal.get()); threadLocal.set(2); } public static void otherMethodB() { System.out.println("threadLocal.get() -> " + threadLocal.get()); }}

结果:

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

TransmittableThreadLocal

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

用TransmittableThreadLocal、TtlRunnable的简单例子:

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;import com.alibaba.ttl.TransmittableThreadLocal;import com.alibaba.ttl.TtlRunnable;public class SubThreadReuseThreadUsage {        private static ThreadLocal
threadLocal = new TransmittableThreadLocal
(); public static void main(String[] args) throws InterruptedException { threadLocal.set(1); /* 声明多线程组件 */ ExecutorService executorService = Executors.newSingleThreadExecutor(); Runnable runnableA = new Runnable() { @Override public void run() { otherMethodA(); } }; Runnable runnableB = new Runnable() { @Override public void run() { otherMethodB(); } }; TtlRunnable ttlRunnableA = TtlRunnable.get(runnableA); TtlRunnable ttlRunnableB = TtlRunnable.get(runnableB); // 运行一个线程 executorService.execute(ttlRunnableA); TimeUnit.SECONDS.sleep(1); // 睡眠,让上面线程跑完 /* 运行一个线程 */ executorService.execute(ttlRunnableB); } public static void otherMethodA() { System.out.println("threadLocal.get() -> " + threadLocal.get()); threadLocal.set(2); } public static void otherMethodB() { System.out.println("threadLocal.get() -> " + threadLocal.get()); }}

结果:

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

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

转载地址:http://gxgpx.baihongyu.com/

你可能感兴趣的文章
问答项目---用户注册的那些事儿(JS验证)
查看>>
Android进阶篇-百度地图获取地理信息
查看>>
返回前一页并刷新页面方法
查看>>
2.3 InnoDB 体系架构
查看>>
不定宽高垂直居中分析
查看>>
项目管理学习笔记之二.工作分解
查看>>
C# PPT 为形状设置三维效果
查看>>
js数组实现不重复插入数据
查看>>
aidl跨进程通讯
查看>>
如何确定所运行的 SQL Server 2005 的版本?
查看>>
我的友情链接
查看>>
老李分享:qtp自动化测试框架赏析-关键字自动化测试框架 2
查看>>
忙里偷闲 -- 工作随笔
查看>>
springboot报编译失败 Compilation failure
查看>>
mysqld error(一)
查看>>
Javascript延时函数
查看>>
UML类图关系大全
查看>>
Ant编译Hadoop 1.0.3的eclipse-plugin插件包
查看>>
tensorflow开发环境搭建
查看>>
JDBCRealm Http Digest
查看>>