热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ThreadLocal机制解析

ThreadLocal在Android开发中,Handler消息处理机制中用到了ThreadLocal类,花了点时间对它进行解析。Thread类中有个成员变量threadLocal

ThreadLocal

在Android开发中,Handler消息处理机制中用到了ThreadLocal类,花了点时间对它进行解析。

Thread类中有个成员变量threadLocals,类型为ThreadLocal.ThreadLocalMap,是ThreadLocal自定义的一个hashmap,它的key是ThreadLocal类型,value是Object。如下:

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

注意:在Thread中threadLocals参数并没有被赋值,所以默认为null

ThreadLocal类中,有这样一个函数

//初始化值
protected T initialValue() {
return null;
}

这个函数用来初始化在一个线程中的初始值,默认返回null,建议使用ThreadLocal时重写。

再看看这个函数

/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}

/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

从setInitialValue()方法名可以看出是设置ThreadLocal的初始值,先是获取当前线程 t ,然后通过getMap(t)方法获取当前线程 t 的threadLocals变量,就是一个ThreadLocalMap实例,通过上面的getMap(Thread t)方法看出返回的map应该是null,所以执行createMap(t,value)方法。

/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

这个方法很关键,通过createMap()方法使Thread的成员变量threadLocals赋值了,并把this当做key,把通过initialValue()方法返回的值作为value(所以重写initialValue()的话就避免了初始化值为null的尴尬)。

同时我们发现在ThreadLocal类中的set()方法也调用了createMap()方法,是不是有种顿时豁然开朗的感觉。

/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

我们先看看哪里调用了setInitialValue()法

/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}

发现整个ThreadLocal类中只有这一个地方调用了这个方法。看了这里就应该明白了吧。

  • 在没有调用set()方法的情况下, 如果第一次调用get()方法,getMap()肯定返回一个null的ThreadLocalMap对象,就会执行 return setInitialValue(); 语句,返回初始化的值。
  • 如果调用了set()方法的情况下,可以看出set方法也调用了createMap(t, value);来给Thread的成员变量threadLocals赋值,那么getMap()肯定就返回一个不为null的ThreadLocalMap对象,把this作为对象来获取value。

小结

  • Thead中有一个类型为ThreadLocalMap的成员变量threadLocals,并且初始值为null
  • ThreadLocalMap是一个ThreadLocal自定义的HashMap,键为ThreadLocal,值为Object
  • ThreadLocal默认会有一个初始值null,你可以通过重写initialValue()方法或者调用set()方法来改变这个初始值,他们的本质都是去调用createMap()方法给当前的线程Thread的成员变量threadLocals赋值。
  • ThreadLocal的set()方法通过获取当前线程 t ,通过 t 的成员变量threadLocals的set()方法来去保存value值,并把当前对象this作为key。
  • 调用ThreadLocal的get()方法来获取value,实质就是获取当前线程t的成员变量threadLocals,并且把自身作为key来获取value的过程。

每个Thread都维护了一个ThreadLocalMap对象,也就是threadLocals变量,通过它就可以保存各种不同类型的ThreadLocal和对应的值

Demo示例:

public class ThreadStudy {
private static OneThread oneThread;
private static TwoThread twoThread;
private static ThreadLocal integerThreadLocal = new ThreadLocal(){
@Override
protected Integer initialValue() {
return 99;
}
};
private static ThreadLocal stringThreadLocal = new InheritableThreadLocal(){
@Override
protected String initialValue() {
return "Hello world";
}
};
public static void main(String[] args) throws ExecutionException, InterruptedException {
OneThread= new ThreadStudy().new OneThread();
twoThread = new ThreadStudy().new TwoThread();
oneThread.start();
twoThread.start();
}
class OneThread extends Thread{
public OneThread(){
}
@Override
public void run() {
//给Thread的threadLocals变量赋值,并把stringThreadLocal作为key,"设置value值"作为value保存在threadLocals里。
stringThreadLocal.set("设置value值");
System.out.println(Thread.currentThread().getName() + " " + stringThreadLocal.get());
}
}
class TwoThread extends Thread{
public TwoThread(){
}
@Override
public void run() {
//由于没有调用set方法,所以会在第一次调用get()方法中去个给Thread的threadLocals变量赋值
System.out.println(Thread.currentThread().getName() + " " + integerThreadLocal.get());
System.out.println(Thread.currentThread().getName() + " " + stringThreadLocal.get());
}
}
}

输出结果:

Thread-0 设置value值
Thread-1 99
Thread-1 Hello world

相信看到这里对ThreadLocal都理解了吧。

话说ThreadLocalMap类到底怎么工作的呢,下面我们一起来看看。

ThreadLocalMap

在ThreadLocalMap中有个静态内部类Entry

/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}

Entry继承了WeakReference(弱引用)类,应该这里准确的说是ThreadLocal作为Entry的key并成了弱引用。

其实在HashMap中也有这样的一个同名的接口类,关系是:HashMap 继承了AbstractMap ,然后AbstractMap 实现了 Map,在Map接口中就有了Entry接口。

/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;

ThreadLocalMap和HashMap一样默认容量16,并且用数组数组实现。在ThreadLoca中通过set方法类添加数据,从源码中可以看出实际是调用了ThreadLocalMap类的set方法,我们就先来看看set方法是怎么实现的吧。

/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
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();
}

int i = key.threadLocalHashCode & (len-1);

通过ThreadLocal的threadLocalHashCode 参数,可以理解为HashCode(int类型hash值,每个ThreadLocal对应一个),和table数组的长度-1的差做“与”运算得到元素在数组中的下标。然后从table数组中取出对应下标的Entry判断是否为null,如果没有发生冲突(取出的Entry == null)则给对应的下标赋值。如果发生了冲突(取出的Entry != null),则比较冲突的Entry的key是否和当前的set(ThreadLoacal key,Object value)的参数key相同,如果是相同的则覆盖原来的value并结束。如果参数key和获取的Entry的key不相等,这个时候就需要解决冲突,这里是通过向后移动下标,即下标 +1(这里和HashMap解决冲突不同)来解决的。

解决冲突如下:

/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 }

那么ThreadLocalMap添加数据的过程完成了。ThreadLocal中通过get方法取出保存的数据,从源码中也可以看出实际是调用了ThreadLocalMap的getEntry方法。

/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}

getEntry先是通过int i = key.threadLocalHashCode & (table.length – 1);取得对应的下标,和前面的set方法的获取下标方式相对应。如果取到的Entry值不为null而且key也相同就返回取到的Entry。由于在添加Entry的时候有可能发生冲突,那么在取得时候就可能不能一次性通过下标取到对应的值,如果发生这样的情况就调用
getEntryAfterMiss()来获取。


/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}

这里必须和前面解决冲突的思路一致,如果没有一次性取到对应的Entry,下标就向后移动(+1),然后在取出新的下标的值进行比较,如果符合条件就返回。如果取出的Entry为null,则返回null。

默认的ThreadLocalMap容量只有16,如果存放的数据多了,那么就跟HashMap一样需要扩容,默认情况下当存储的数据量超过容量的2/3的时候就会扩容为之前容量的2倍。

/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}

/**
* Double the capacity of the table.
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j Entry e = oldTab[j];
if (e != null) {
ThreadLocal k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}

相信你看到这里对ThreadLocal有更深入的理解了吧。


推荐阅读
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
author-avatar
丶Le丨囧囧_832
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有