前言

说起ThreadLocal大家应该有种很熟悉的感觉,但是又好像不知道是干啥用的,第一次接触它还是在Looper的源码中,每次获取Looper对象是,通过ThreadLocal的get方法获取到当前线程的Looper对象,有兴趣的可以看看之前的文章Android源码学习之handler,为什么要通过ThreadLocal来获取Looper对象呢,亦或者说这样做有什么好处?今天就带大家一起深入了解这个神秘的ThreadLocal。

源码

话不多说,直接开撸:

/*** This class provides thread-local variables.  These variables differ from* their normal counterparts in that each thread that accesses one (via its* {@code get} or {@code set} method) has its own, independently initialized* copy of the variable.  {@code 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).*/
public class ThreadLocal<T> {private final int threadLocalHashCode = nextHashCode();private static AtomicInteger nextHashCode =new AtomicInteger();private static final int HASH_INCREMENT = 0x61c88647;private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);}public ThreadLocal() {}public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}
}
复制代码

从类上面的注释可以看到,大概翻译下也就是:该类提供线程局部变量,这些变量与正常的变量不同,而是每个访问一个的线程都有自己独立初始化的变量副本,ThreadLocal实例通常是类中的私有静态字段,希望将状态与线程关联

不要羡慕鄙人的英语,因为。。我是google翻译的...(咳咳)

这里只是摘了一段代码,从上面暴露的方法可以看到,提供了set,get方法,很明显就能看出来,set方法时,key是this,也就是当前的ThreadLocal对象,value就是传递进来的值,而最终是存储到哪呢,一个叫ThreadLocalMap的对象,追踪一下,发现它其实是ThreadLocal的静态内部类:

static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}private void set(ThreadLocal<?> key, Object value) {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();}}
}复制代码

Set方法

可以看到内部再维护了一个静态Entry类继承弱引用,所以上面所说的key,ThreadLocal对象其实是咦弱引用的形式存储的,这样也有益于GC回收,防止内存泄漏,我们先来看set方法:

  • 通过key的哈希码和数组长度,计算出存储元素的下标,这点应该很类似于HashMap中的找数组下标的方式。
  • 找到下标之后,一个循环,从i开始往后一直遍历到数组最后一个Entry,如果key相等,覆盖value,如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
  • 如果找到下标为空的元素,跳出循环,将key和value,设置进去,填满该下标元素位置,同时size++,如果超过阈值,重新hash
    private void rehash() {//清理一次旧的数据expungeStaleEntries();//如果当前size大于3/4的阈值,就进行扩容if (size >= threshold - threshold / 4)resize();}private void resize() {Entry[] oldTab = table;int oldLen = oldTab.length;//将长度扩容到之前的2倍int newLen = oldLen * 2;Entry[] newTab = new Entry[newLen];int count = 0;for (int j = 0; j < oldLen; ++j) {Entry e = oldTab[j];if (e != null) {ThreadLocal<?> k = e.get();//取出ThreadLocal对象if (k == null) {e.value = null; // Help the GC} else {//如果不为空,类似上面的循环,一直找到一个没有使用的位置,在                     空节点上塞入Entryint h = k.threadLocalHashCode & (newLen - 1);while (newTab[h] != null)h = nextIndex(h, newLen);newTab[h] = e;count++;}}}setThreshold(newLen);size = count;table = newTab;}复制代码

大部分注释,其实都是根据里面的英文注释翻译过来的,所以想了解的可以静下心来好好的翻一翻源码,相信我,你会有意外的收获。

Get方法

   public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);//通过当前线程,获取ThreadLocalMap,如果不为空,返回value,否则走初始化流程if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {//初始化,设置阈值位int值16table = new Entry[INITIAL_CAPACITY];//计算数组下标int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}//阈值设置为容量的*2/3,即负载因子为2/3,超过就进行再哈希private void setThreshold(int len) {threshold = len * 2 / 3;}
复制代码
  • 从当前线程中获取ThreadLocalMap,查询当前ThreadLocal变量实例对应的Entry,如果不为null,获取value,否则进入初始化
  • 初始化,设置数组初始长度,阈值等等参数

总结

  • ThreadLocal内部维护ThreadLocalMap,功能大致类似于HashMap,内部静态Entry类,key是ThreadLocal对象本身,value是传递来的对象,真正实现结构是数组,不断的扩容插入数据。
  • ThreadLocal解决线程局部变量统一定义问题,并不是用来用来解决线程安全问题的,因为本身就是多线程不共享的,是不存在同步竞争的关系的,保证线程本地变量且只能单个线程内维护使用
  • 本文只给出了大概代码,主要看ThreadLocalMap类代码,内部如何实现了一套定制的线性探测hash表以及高效的垃圾清理机制
  • 对于Hash冲突,也就是当经历过hash计算出下标,发现位置上是有人的,ThreadLocalMap和HashMap的处理方式有所不同:
    • ThreadLocalMap:比较直接简单,如果发生冲突,将下标i加1,不断的进行遍历整个数组,找到空的位置放置数据,同时计算当前size是否超过阈值,如果超过,就扩容,每次扩容成之前的2倍。
    • HashMap:JDK1.8之前内部是数组+链表实现的,1.8是数组+红黑树,这里说链表的实现方式,JDK1.8源码还没有详细读过,链表的话,遍历链表,看是否有key相同的节点,有则更新value值,没有则新建节点,此时若链表数量大于阀值8,就进行扩容,由于hash的平均性,这样的效率明显会比ThreadLocalMap高不少,有兴趣可以看下这篇文章,你想要的HashMap都在这里。

相关文章阅读:

3分钟带你看懂android的Binder机制

Activity不用注册?手把手教你Hook

Android源码学习之handler

求求你们不要再问HashMap原理了....

零基础带你吃掉JNI全家桶

请帮顶 / 评论点赞!因为你的鼓励是我写作的最大动力!

重走JAVA之路(四):ThreadLocal源码解析相关推荐

  1. 面试官系统精讲Java源码及大厂真题 - 43 ThreadLocal 源码解析

    43 ThreadLocal 源码解析 引导语 ThreadLocal 提供了一种方式,让在多线程环境下,每个线程都可以拥有自己独特的数据,并且可以在整个线程执行过程中,从上而下的传递. 1 用法演示 ...

  2. 死磕 java同步系列之ReentrantReadWriteLock源码解析

    问题 (1)读写锁是什么? (2)读写锁具有哪些特性? (3)ReentrantReadWriteLock是怎么实现读写锁的? (4)如何使用ReentrantReadWriteLock实现高效安全的 ...

  3. clickhouse原理解析与开发实战 pdf_重识SSM,“超高频面试点+源码解析+实战PDF”,一次性干掉全拿走...

    重识SSM,"超高频面试点"+"源码解析"+"实战PDF",一次性干掉全拿走!! 01 超高频面试点知识篇 1.1 Spring超高频面试点 ...

  4. 《Java修炼指南:高频源码解析》阅读笔记一Java数据结构的实现集合类

    一.Arrays工具类 来自java.util.Arrays,用来处理数组的各种方法. 1.1 List asList(T- a) 用来返回由自定数组支持的固定大小列表,虽然这里返回了一个List,但 ...

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

    1 一句话概括ThreadLocal   什么是ThreadLocal?顾名思义:线程本地变量,它为每个使用该对象的线程创建了一个独立的变量副本. 2 ThreadLocal使用场景   用一句话总结 ...

  6. Java并发编程之CountDownLatch源码解析

    一.导语 最近在学习并发编程原理,所以准备整理一下自己学到的知识,先写一篇CountDownLatch的源码分析,之后希望可以慢慢写完整个并发编程. 二.什么是CountDownLatch Count ...

  7. java容器三:HashMap源码解析

    前言:Map接口 map是一个存储键值对的集合,实现了Map接口的主要类有以下几种 TreeMap:用红黑树实现 HashMap:数组和链表实现 HashTable:与HashMap类似,但是线程安全 ...

  8. 深读源码-java同步系列之StampedLock源码解析

    问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...

  9. 【Java学习002】Java-ArrayList源码解析

    ArrayList源码解析 1.1底层数据结构 定义:实现List接口的可扩容数组实现. 数组特点: 查询快:数组开辟的是连续空间,所以可以依靠索引进行快速查询. 增删慢:每次删除元素,都需要更改数组 ...

  10. Java并发编程之FutureTask源码解析

    上次总结一下AQS的一些相关知识,这次总结了一下FutureTask的东西,相对于AQS来说简单好多呀 之前提到过一个LockSupport的工具类,也了解一下这个工具类的用法,这里也巩固一下吧 /* ...

最新文章

  1. 如何写出优雅的 Golang 代码
  2. STL中用erase()方法遍历删除元素
  3. docker 安装redis第三方集群方案 codis
  4. 从OpenFOAM的源码中查找信息
  5. 电脑:电脑弹窗广告三个解决方法,欢迎收藏!
  6. Flowable 数据库表结构 ACT_HI_PROCINST
  7. 财富自由之路,其实取决于你的工作效率
  8. Wait 线程阻塞 与 Notify、NotifyAll 线程唤醒
  9. 2021年高压电工模拟考试系统及高压电工考试试题
  10. 简易电影售票系统(附部分总结)
  11. 漫画 前端发展史的江湖恩怨情仇~
  12. ESP8266WiFi模块实现代码
  13. 官方VM tools下载地址
  14. 关于微信小程序如何使用Vant组件
  15. Docker入门到实践 (一) docker简介与安装、常用命令讲解
  16. Cannot read property ‘$message‘ of undefined
  17. PHP 蒙太奇马赛克拼图,AndreaMosaic制作一幅马赛克拼图
  18. 存储中所说的蓝光和磁带库
  19. 云呐|固定资产计提折旧怎么算
  20. java保存cookie在本地_Java保存Cookie

热门文章

  1. StringUtils测试
  2. android BaseAdapter多布局缓存
  3. 插件占坑,四大组件动态注册前奏(三) 系统BroadCast的注册发送流程
  4. 打造一流云计算机房,【迈向“双一流”】为科研插上云计算的“翅膀”
  5. 怎么避免后台被搜索_优化亚马逊后台关键词的6个技巧,让买家快速找到你
  6. python整数类型与数学-Python类型和运算--数字
  7. java三元运算符_我把Java基础编程及思维导图整理的超级详细,小白都能看懂
  8. 如何给表格数据加顺序号php,通过layui给数据表格添加序号
  9. c4d序列号_(图文+视频)野分享:手把手教你免费获取Megascans所有资产并应用于C4D...
  10. ping包优化版本python