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

ThreadLocal的使用和实现原理

概述ThreadLocal是一个将指定值储存到指定线程的类。简单的讲ThreadLocal可以指定储存数据类型,然后在不同线程中设置某个值,这个值在其他

概述


ThreadLocal是一个将指定值储存到指定线程的类。简单的讲ThreadLocal可以指定储存数据类型,然后在不同线程中设置某个值,这个值在其他线程是不可以获取到的,只能在本线程才能获取到。平常中很少使用到ThreadLocal,但在也不少见到,比如Looper类中就有使用到它。


使用

ThreadLocal的使用不难,就是先创建ThreadLocal对象并指定要存储的类型,分别在各个线程中存放指定的值即可。

private ThreadLocal<String>mThreadLocal&#61;new ThreadLocal<>();//创建ThreadLocal对象并指定存储String类型mThreadLocal.set("main");//在主线程中存储"main"字符串Thread t1&#61;new Thread(new Runnable() {&#64;Overridepublic void run() {mThreadLocal.set("t1");//在t1线程中存储"t1"字符串Log.i(TAG, "t1-run:"&#43;mThreadLocal.get());}});Thread t2&#61;new Thread(new Runnable() {&#64;Overridepublic void run() {在t2线程中不存储任何值&#xff0c;直接获取Log.i(TAG, "t2-run:"&#43;mThreadLocal.get());}});t1.start();t2.start();Log.i(TAG, "main-run:"&#43;mThreadLocal.get());

最后日志输出的结果为&#xff1a;

com.sendi.threadlocaltest I/MainActivity: main-run:main
com.sendi.threadlocaltest I/MainActivity: t2-run:null
com.sendi.threadlocaltest I/MainActivity: t1-run:t1

根据输出的结果&#xff0c;很明显看出不同线程只能获取到各自存储的值&#xff0c;没有存储值则获取到null。它有点类似于HashMap&#xff0c;但实际内部实现却不同。
以下是它内部实现原理的解析。
在看源码之前&#xff0c;先上一张关系图
这里写图片描述

实现原理


  • 首先从它是set方法入手

public void set(T value) {Thread t &#61; Thread.currentThread();//获取当前线程ThreadLocalMap map &#61; getMap(t);//根据当前线程去获取到一个ThreadLocalMap对象if (map !&#61; null)map.set(this, value);//将value设置进ThreadLocalMap中elsecreateMap(t, value);//创建一个ThreadLocalMap对象&#xff0c;并将value存储到里边去}

createMap(Thread, T)方法源码如下&#xff1a;它创建一个ThreadLocalMap对象&#xff0c;并将该对象赋值给当前线程的threadLocals成员

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

getMap(Thread)方法比较简单&#xff0c;就是根据当前线程去获取到对应的ThreadLocalMap对象。

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

接下来看看ThreadLocalMap的set方法。

由于关注的是ThreadLocal的对数据的存储和获取&#xff0c;所以这里我们只看 ThreadLocalMap的set和get方法&#xff1a;

  • ThreadLocalMap的set(ThreadLocal key, Object value)方法实现

private void set(ThreadLocal key, Object value) {Entry[] tab &#61; table;int len &#61; tab.length;//通过ThreadLocal去获取到在数组中的索引int i &#61; key.threadLocalHashCode & (len-1);//遍历Entry数组for (Entry e &#61; tab[i];e !&#61; null;e &#61; tab[i &#61; nextIndex(i, len)]) {ThreadLocal k &#61; e.get();//如果遍历到的Entry对象中的引用ThreadLocal与传进来的key相同时&#xff0c;//则将传进来的value赋值给Entry中的valueif (k &#61;&#61; key) {e.value &#61; value;return;}//如果遍历到的Entry对象中的引用ThreadLocal为null时&#xff0c;//则调用replaceStaleEntry()方法&#xff0c;此方法实现的大概思路在下边说明if (k &#61;&#61; null) {replaceStaleEntry(key, value, i);return;}}//如果以上两种情况都没有发生&#xff0c;则ThreadLocal和value封装成Entry并添加进Entry数组中tab[i] &#61; new Entry(key, value);int sz &#61; &#43;&#43;size;//长度加1//判断是否需要扩容if (!cleanSomeSlots(i, sz) && sz >&#61; threshold)rehash();}

  • replaceStaleEntry方法&#xff0c;里边大概逻辑是&#xff1a;
    接着i索引往下遍历找到一个存储的key与传进来的key相同的Entry对象&#xff0c;然后替换传进来的key和value&#xff0c;否则把传进来的ThreadLocal和value封装成Entry并添加进Entry数组中&#xff0c;最后决定要清除哪一些旧的Entry。

小总结&#xff1a;

  1. ThreadLocal的set方法里边的逻辑就是根据所在线程获取一个ThreadLocalMap对象&#xff0c;
  2. 如果对象不为null&#xff0c;则调用ThreadLocalMap的set方法&#xff0c;
  3. 否则创建一个ThreadLocalMap对象并赋值给线程的threadLocals属性。
  4. 在ThreadLocalMap的set方法中&#xff0c;遍历Entry数组&#xff0c;如果遍历到的Entry对象中的ThreadLocal对象与set方法传进来的key相同时&#xff0c;则直接将传进来的value替换Entry中的value值
  5. 如果Entry对象中的ThreadLocal对象为null&#xff0c;则继续从i向后遍历Entry数组&#xff0c;如果set方法传进来的key存在于后续的Entry对象&#xff08;后面用A对象表示&#xff09;中&#xff0c;则将A对象的value换为set方法传进来的value&#xff0c;然后将Entry[i]与A对象互换。

到这里可以知道值其实不是存储在ThreadLocal中&#xff0c;而是存储在ThreadLocalMap的Entry数组中的某个Entry中。实际上ThreadLocal只是作为Entry中的key。

  • 接下来是ThreadLocal的get方法

public T get() {Thread t &#61; Thread.currentThread();ThreadLocalMap map &#61; getMap(t);//根据所在线程获取ThreadLocalMap 对象if (map !&#61; null) {//根据ThreadLocal获取对应的Entry对象ThreadLocalMap.Entry e &#61; map.getEntry(this);if (e !&#61; null) {&#64;SuppressWarnings("unchecked")//获取Entry对象中的ValueT result &#61; (T)e.value;return result;}}//setInitialValue方法里边的逻辑跟set方法差不多&#xff0c;//只是将Value初始化为null在存储进去&#xff1b;然后返回一个nullreturn setInitialValue();}

  • ThreadLocalMap 中的getEntry方法的实现

private Entry getEntry(ThreadLocal key) {//根据当前的ThreadLocal对象来获取对应的索引int i &#61; key.threadLocalHashCode & (table.length - 1);Entry e &#61; table[i];if (e !&#61; null && e.get() &#61;&#61; key)return e;else//当ThreadLocal对应的Entry对象不存在时调用此方法//此方法的逻辑在下边说明return getEntryAfterMiss(key, i, e);}

  • getEntryAfterMiss(key, i, e)的逻辑&#xff1a;继续向下寻找一个与传进来的ThreadLocal对象相同的key所在的Entry&#xff0c;同时对key为null的Entry进行清除。

小总结

  1. ThreadLocal的get方法中&#xff0c;通过所在线程去获取ThreadLocalMap对象
  2. 通过ThreadLocalMap对象获取Entry对象
  3. ThreadLocalMap在获取Entry对象的过程中&#xff0c;同时对存放ThreadLocal为null的Entry进行清除

最后是ThreadLocalMap中的结构&#xff1a;

ThreadLocalMap是ThreadLocal的一个静态内部类

static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal> {Object value;//真正把值存放在此Entry(ThreadLocal k, Object v) {super(k);value &#61; v;}}//默认Entry数组容量private static final int INITIAL_CAPACITY &#61; 16;//Entry数组private Entry[] table;//当前元素长度private int size &#61; 0;//闸值&#xff0c;当当前元素长度大于等于此值时&#xff0c;会进行扩容//它等于 容量*2/3private int threshold;
}

总结


  • 不同线程中存放着不同的ThreadLocalMap对象
  • ThreadLocal并没有存放值&#xff0c;它只是充当key的角色&#xff0c;根据它的hash值可计算出在Entry数组中的位置&#xff0c;实际上值是存放在它的一个内部类ThreadLocalMap中的Entry
  • 在每次的get和set方法中&#xff0c;都会对key为null的Entry进行清除

推荐阅读
author-avatar
秋夜里的寂寞
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有