前言

废话不多说,先了解什么是threadLocal,下面是threadLocal类的说明注释,

这段话大致(猜的)意思是,改类为线程提供了一个局部变量,但是呢,这个变量和普通的变量又有所不同,怎么不同呢,那就是这个类提供的线程的变量只能被该线程访问,别的线程访问不了,也就是说,这个局部变量是该线程私有的,不与别人分享的。那么问题来了:

  1. 线程为什么要一个这么个私有的别人不能访问的变量呢,存在即合理,他存在的意义是什么呢
  2. 这个变量又是怎么保存的呢

存在即合理

是不是我们再开发中有时候有这种需求,我们在一个线程中,需要一个类似于一个会话级别session级别的缓存的额东西,我们把一些变量信息保存进去,然后再这个线程里随取随用,但是又不会干扰其他线程的变量,可能一些老司机脑海里已经出现一个词,对,线程的上下文,类似于一个线程级别的上下文,随着线程的销毁而销毁。那么恭喜你,threadLocal可以完美的解决的您的问题,只要您定义好您的threadLocal对象,并且随时可以拿到这个对象(譬如,定义成某个类的静态变量),然后实现了在线程里面使用该对象一次set把一个user对象放进该threadLocal对象中,到处使用threadLocal对象get了,是不是很清爽呀。可能这时候有同学就会举手了,我可以把我的user设置在父线程里面或干脆设置为静态常量,然后岂不是更清爽吗,如果这么想的话是没错,但是前提是,你要保证你的user是线程安全的哦,如果没有实现线程安全,我的个乖乖,多个线程访问一个对象,其结果我就不用说了吧。对的,我们的threadLocal可以让你享受清爽的同时,还能保证你的线程安全(千万不要吧父变量放到threadLocal里面)。

  小结:threadLocal可以让我们清爽的写代码使用变量同时,还能贴心的为我们解决线程安全的问题。

变量是怎么保存到threadLocal里面呢

  话不多说,上代码:

 public void set(T value) {
     //获取当前线程实例
Thread t = Thread.currentThread();
     //获取一个threadLocalMap对象 
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

这是我们的ThreadLocal类的set方法,大家看方法名应该也才出来这个方法是干啥的吧,yes,我们就是通过这个方法把我们的user保存到threadLocal中去的。首先该方法调用了一个获取当前线程实例的方法,接着呢又拿着当前线程的对象获取了一个叫ThreadLocalMap的对象,然后判断把我们的user set到了这个map里面了。

问题来了,这个map是什么鬼,哪里来的呢

  想要知道这个map哪里来的,我们只需把getMap(t)这个方法扒出来,是不是就一目了然呢。扒出来看一下:

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

代码如此简单,返回线程参数的一个变量。但是简单的代码却告诉了我们三个信息

  1. 当前线程对象实例有个ThreadLocalMap变量(废话)
  2. 我们获取的map对象原来是当前线程实例的一个变量(也是废话)
  3. 原来我们调用set方法时候,是把我们要保存的实例放到了当前线程实例的一个threadLocalMap变量里面(划重点)

但是:

问题又来了,这个ThreadLocalMap又是个什么鬼

我们打开TreadLocalMap类可以发现,TreadLocalMap是属于ThreadLocal的一个静态内部类,该类又有一个内部类Entry,和一个Entry的数组变量,而我们所要保存的实例最终也是通过Entry保存在这个数组里面的,下面开始扒代码:

      static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

上面代码是ThreadLocalMap的内部类Entry,我们可以看到,该类继承类一个弱引用类WeakReference,构造函数实现类父类的构造,并把参数value复制给成员变量value,那么问题来类,问什么要设置这么一个内部类呢,我们继续扒代码,

private Entry[] table;
原来在我们的ThreadLocalMap里面还有一个Entry的数组变量,那么为什么要定义这么一个数组变量呢,我们 继续扒代码
 private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not. Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get(); if (k == key) {
e.value = value;
return;
} if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
} tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

是不是这方法有点熟悉,没错,我们在扒threadLocal的set方法代码时,使用当前线程获取的threadLocalMap对象就是调用该方法把我们的实例set保存起来类,参数分别是我们定义的threadLocal对象和我们要保存的实例。分析代码我们发现,原来该方法使用我们传递的threadLocal对象和当前threadLocalMap的entry数组长度进行对位操作获取下标,然后将我们的实例放在entry实例里面,然后把该entry对象放在threadLocal的entry数组里面,至此,保存全部完毕。

同样我们取的时候也是沿着原来的路子,首先获取当前线程,然后获取当前线程的threadLocalMap变量,通过getEntry方法再把entry从该变量的entry数组中取出来,然后再把value取出来。至此,完美的存取就完成了。由于我们的实例实际上是保存在当前的线程实例的变量中,所以会随着线程的结束而销毁,并且成功实现了多线程之间的数据隔离。

结论

原来我们的user是这么被保存到线程局部变量的:

  1. 声明threadLocal对象
  2. 调用ThreadLocal.set
  3. 获取当前线程threadLocalMap变量,并调用该变量set方法将userf放到一个entry对象里面,然后再把该对象放到threadLocalMap变量的entry数组里面,下标为当前threadLocal对象

实际业务场景使用

我们现在需要实现一个上线问存取userId的功能,在线程隔离的前提下,把我们的userId设置到上下文后,可以随时取出

首先定义我们的上下文类SystemContext

public class SystemContext {

    private transient static ThreadLocal<Map<String, String>> contextMap = new ThreadLocal<>();

    private static final String KEY_USER_ID = "userId";

    public static void setContextMap(ThreadLocal<Map<String, String>> contextMap) {
SystemContext.contextMap = contextMap;
} private static String get(String key) {
Map<String, String> map = contextMap.get();
if (null == map) {
return null;
}
return map.get(key);
} public static String getUserId() {
return get(KEY_USER_ID);
} public static void setUserId(String value) { Map<String, String> map = contextMap.get();
if (null == map) {
map = new HashMap<>();
} if (null == value) {
map.remove(KEY_USER_ID);
} map.put(KEY_USER_ID, value);
contextMap.set(map);
} }

main方法开启多个线程,将userId设置到上下文后打印输出:

public class ThreadLocalMain {

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i <10; i++) {
int i1 = i + 1; Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//设置userId
SystemContext.setUserId("userId"+i1); String threadName = Thread.currentThread().getName(); //打印
System.out.println(threadName + " - " + "userId = " + SystemContext.getUserId());
}
}); //定义线程名称
thread.setName("thread"+i1); executorService.execute(thread); }
}
}

打印结果:

从打印结果可以看出,我们成功的取出了我们放置的userId,并且成功的实现了线程之间的数据隔离(使用静态成员变量实现不了线程隔离),即我们的userId都是私有的,线程之间互不干扰

  当我们使用线程池的时候,线程池任务结束以后要记得要调用threadLocal.remove(),因为线程池里面的线程是持久化存在的,也就是说,当前任务执行完之后并不会马上销毁线程,甚至永远不会销毁。而前面我们也提到过,线程的threadLocal存放的线程局部变量是随着线程的销毁而销毁的 ,所以如果我们任务执行完后不清除当前线程的threadLocal的话,而只是一味往里面放东西的话,那就会造成内存泄漏。

手打不易,菜鸟一枚,如有不对实属正常,欢迎大家多多指正

threadLocal源码土话解说的更多相关文章

  1. Java多线程9:ThreadLocal源码剖析

    ThreadLocal源码剖析 ThreadLocal其实比较简单,因为类里就三个public方法:set(T value).get().remove().先剖析源码清楚地知道ThreadLocal是 ...

  2. Java多线程学习之ThreadLocal源码分析

    0.概述 ThreadLocal,即线程本地变量,是一个以ThreadLocal对象为键.任意对象为值的存储结构.它可以将变量绑定到特定的线程上,使每个线程都拥有改变量的一个拷贝,各线程相同变量间互不 ...

  3. Java并发编程之ThreadLocal源码分析

    ## 1 一句话概括ThreadLocal<font face="微软雅黑" size=4>  什么是ThreadLocal?顾名思义:线程本地变量,它为每个使用该对象 ...

  4. ThreadLocal源码解读

    1. 背景 ThreadLocal源码解读,网上面早已经泛滥了,大多比较浅,甚至有的连基本原理都说的很有问题,包括百度搜索出来的第一篇高访问量博文,说ThreadLocal内部有个map,键为线程对象 ...

  5. 并发编程(四)—— ThreadLocal源码分析及内存泄露预防

    今天我们一起探讨下ThreadLocal的实现原理和源码分析.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给出了两 ...

  6. ThreadLocal详解,ThreadLocal源码分析,ThreadLocal图解

    本文脉路: 概念阐释 ---->  原理图解  ------> 源码分析 ------>  思路整理  ----> 其他补充. 一.概念阐述. ThreadLocal 是一个为 ...

  7. 【JAVA】ThreadLocal源码分析

    ThreadLocal内部是用一张哈希表来存储: static class ThreadLocalMap { static class Entry extends WeakReference<T ...

  8. java多线程17:ThreadLocal源码剖析

    ThreadLocal源码剖析 ThreadLocal其实比较简单,因为类里就三个public方法:set(T value).get().remove().先剖析源码清楚地知道ThreadLocal是 ...

  9. 并发-ThreadLocal源码分析

    ThreadLocal源码分析 参考: http://www.cnblogs.com/dolphin0520/p/3920407.html https://www.cnblogs.com/coshah ...

随机推荐

  1. 04.开发REST 接口

    使用Django开发REST 接口 我们以在Django框架中使用的图书英雄案例来写一套支持图书数据增删改查的REST API接口,来理解REST API的开发. 在此案例中,前后端均发送JSON格式 ...

  2. 【Spring注解驱动开发】如何使用@Bean注解指定初始化和销毁的方法?看这一篇就够了!!

    写在前面 在[String注解驱动开发专题]中,前面的文章我们主要讲了有关于如何向Spring容器中注册bean的知识,大家可以到[String注解驱动开发专题]中系统学习.接下来,我们继续肝Spri ...

  3. WeChair项目Beta冲刺(5/10)

    团队项目进行情况 1.昨日进展    Beta冲刺第五天 昨日进展: 前后端并行开发,项目按照计划有条不絮进行 2.今日安排 前端:扫码占座功能和预约功能并行开发 后端:扫码占座后端逻辑开发,预约用座 ...

  4. IDEA解决SVN频繁弹出登录框

    将HTTP请求改成SVN就可以了,或者请项目经理开启SVN中的HTTP请求

  5. Flask项目实战:创建电影网站-创世纪(1)

    以后要养成写博客的习惯,用来做笔记.本人看的东西很多很杂,但因为工作中很少涉及,造成看了之后就忘,或者看了就看了,但是没有融入的自己的知识体系里面. 写博客一方面是做记录,一方面是给这段时间业余学习的 ...

  6. dart快速入门教程 (6)

    6.内置操作方法和属性 6.1.数字类型 1.isEven判断是否是偶数 int n = 10; print(n.isEven); // true 2.isOdd判断是否是奇数 int n = 101 ...

  7. Python 图像处理 OpenCV (12): Roberts 算子、 Prewitt 算子、 Sobel 算子和 Laplacian 算子边缘检测技术

    前文传送门: 「Python 图像处理 OpenCV (1):入门」 「Python 图像处理 OpenCV (2):像素处理与 Numpy 操作以及 Matplotlib 显示图像」 「Python ...

  8. Java多线程之synchronized详解

    目录 synchronized简介 同步的原理 对象头与锁的实现 锁的优化与升级 Monitor Record 锁的对比 synchronized简介 synchronized关键字,一般称之为&qu ...

  9. JavaScript手写new方法

    1.看一下正常使用的new方法 function father(name){ this.name=name; this.sayname=function(){ console.log(this.nam ...

  10. c语言学习笔记第二章———入门

    B站有视频演示 2.1软件安装 推荐软件 1.dev-c++ 下载链接:(腾讯软件管家的下载地址) https://sm.myapp.com/original/Development/Dev-Cpp_ ...