前言
ThreadLocal为变量在每个线程中都创建了一个副本,所以每个线程可以访问自己内部的副本变量,不同线程之间不会互相干扰。本文会基于实际场景介绍ThreadLocal如何使用以及内部实现机制。
应用场景
Parameter对象的数据需要在多个模块中使用,如果采用参数传递的方式,显然会增加模块之间的耦合性。先看看用ThreadLocal是如何实现模块间共享数据的。
class Parameter {
private static ThreadLocal<Parameter> _parameter= new ThreadLocal<>();
public static Parameter init() {
_parameter.set(new Parameter());
}
public static Parameter get() {
_parameter.get();
}
...省略变量声明
}
  1. 在模块A中通过Parameter.init初始化。
  2. 在模块B或模块C中通过Parameter.get方法可以获得同一线程中模块A已经初始化的Parameter对象。
实现原理
从线程Thread的角度来看,每个线程内部都会持有一个对ThreadLocalMap实例的引用,ThreadLocalMap实例相当于线程的局部变量空间,存储着线程各自的数据,具体如下:

Entry
Entry继承自WeakReference类,是存储线程私有变量的数据结构。ThreadLocal实例作为引用,意味着如果ThreadLocal实例为null,就可以从table中删除对应的Entry。
class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap
内部使用table数组存储Entry,默认大小INITIAL_CAPACITY(16),先介绍几个参数:
  • size:table中元素的数量。
  • threshold:table大小的2/3,当size >= threshold时,遍历table并删除key为null的元素,如果删除后size >= threshold*3/4时,需要对table进行扩容。
ThreadLocal.set() 实现
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
} ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
从上面代码中看出来:
  1. 从当前线程Thread中获取ThreadLocalMap实例。
  2. ThreadLocal实例和value封装成Entry。
接下去看看Entry存入table数组如何实现的:

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();
}
  1. 通过ThreadLocal的nextHashCode方法生成hash值。

    private static AtomicInteger nextHashCode = new AtomicInteger();
    private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    从nextHashCode方法可以看出,ThreadLocal每实例化一次,其hash值就原子增加HASH_INCREMENT。
    1. 通过 hash & (len -1) 定位到table的位置i,假设table中i位置的元素为f。
    2. 如果f != null,假设f中的引用为k:
    • 如果k和当前ThreadLocal实例一致,则修改value值,返回。
    • 如果k为null,说明这个f已经是stale(陈旧的)的元素。调用replaceStaleEntry方法删除table中所有陈旧的元素(即entry的引用为null)并插入新元素,返回。
    • 否则通过nextIndex方法找到下一个元素f,继续进行步骤3。
    1. 如果f == null,则把Entry加入到table的i位置中。
    2. 通过cleanSomeSlots删除陈旧的元素,如果table中没有元素删除,需判断当前情况下是否要进行扩容。
    table扩容
    如果table中的元素数量达到阈值threshold的3/4,会进行扩容操作,过程很简单:

    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 < oldLen; ++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;
    }
    1. 新建新的数组newTab,大小为原来的2倍。
    2. 复制table的元素到newTab,忽略陈旧的元素,假设table中的元素e需要复制到newTab的i位置,如果i位置存在元素,则找下一个空位置进行插入。
    ThreadLocal.get() 实现

    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();
    } 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);
    }
    获取当前的线程的threadLocals。
    1. 如果threadLocals不为null,则通过ThreadLocalMap.getEntry方法找到对应的entry,如果其引用和当前key一致,则直接返回,否则在table剩下的元素中继续匹配。
    2. 如果threadLocals为null,则通过setInitialValue方法初始化,并返回。
      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;
      }
      总结
      希望通过本文的介绍,大家可以对ThreadLocal有一个更加直观清晰的认识。

深入浅出ThreadLocal的更多相关文章

  1. 深入浅出 ThreadLocal(一)

    本文参考http://lavasoft.blog.51cto.com/62575/51926/,对其中的程序进行了改写 一.概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线 ...

  2. ThreadLocal学习资料

    下面的这一段代码运行起来,就会发生线程安全问题: 启动两个线程,同时去修改 name 属性值. package com.liwei.thread; /** * 下面的代码演示了线程安全发生的由来 * ...

  3. 计算机程序的思维逻辑 (82) - 理解ThreadLocal

    本节,我们来探讨一个特殊的概念,线程本地变量,在Java中的实现是类ThreadLocal,它是什么?有什么用?实现原理是什么?让我们接下来逐步探讨. 基本概念和用法 线程本地变量是说,每个线程都有同 ...

  4. Java编程的逻辑 (82) - 理解ThreadLocal

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  5. 深入浅出微服务框架dubbo(一):基础篇

    一.基础篇 1.1 开篇说明 dubbo是一个分布式服务框架,致力于提供高性能透明化RPC远程调用方案,提供SOA服务治理解决方案.本文旨在将对dubbo的使用和学习总结起来,深入源码探究原理,以备今 ...

  6. 深入浅出ReentrantReadWriteLock源码解析

    读写锁实现逻辑相对比较复杂,但是却是一个经常使用到的功能,希望将我对ReentrantReadWriteLock的源码的理解记录下来,可以对大家有帮助 前提条件 在理解ReentrantReadWri ...

  7. ThreadLocal简单理解

    在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...

  8. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  9. Android线程管理之ThreadLocal理解及应用场景

    前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...

随机推荐

  1. AdaBoost的java实现

    目前学了几个ML的分类的经典算法,但是一直想着是否有一种能将这些算法集成起来的,今天看到了AdaBoost,也算是半个集成,感觉这个思路挺好,很像人的训练过程,并且对决策树是一个很好的补充,因为决策树 ...

  2. android测试之——mokeyrunner上(二)

    以下是本人原创,如若转载和使用请注明转载地址.本博客信息切勿用于商业,可以个人使用,若喜欢我的博客,请关注我,谢谢!博客地址 感谢您支持我的博客,我的动力是您的支持和关注!如若转载和使用请注明转载地址 ...

  3. input的onchange事件实际触发条件与解决方法

    input中onchange事件已经属于元老级别了,并且现在同onclick一样使用频率很高,然而onchange的机制实际上有很多童鞋并不清楚,我们通过实例来分析这个事件的特征. 触发onchang ...

  4. APP导致界面卡死,iPhone卡死

    实测,是 Reachability 类创建实例过多导致 http://stackoverflow.com/questions/34063166/ios-9-app-freeze-with-consol ...

  5. innodb系统表空间维护

    环境说明: 有一个在运行中的mysql环境,由于之前的配置文件设置的过于简单(没有配置innodb_data_file_path变更):造成现在系统表空间已经满了 如果innodb_data_file ...

  6. STM32F412应用开发笔记之八:迪文串口屏显示驱动

    迪文的显示屏使用起来比较方便,其使用串口通讯,即可支持RS232,又可以支持TTL电平.在NUCLEO-F412ZG实验板上,USART2已经引到了CN9上,我们就利用USART2来实现与迪文串口屏的 ...

  7. git 关联远程库(https协议)

    1.在oschina上新建库 2.在本地文件夹右键->"git Bash here" 3.设置全局变量: git config --global user.name &quo ...

  8. 今天学习了下,如何破解wifi

    破解了隔壁的wif,得罪了,哥哥要蹭网一段时间. 主要思路:安装linux环境.这里我选择了虚拟机加cdlinux .运行里面的min抓得握手包,然后淘宝花10元跑下包,因为自己的电脑不行,跑费时间太 ...

  9. 创业类网站建设日志1——搭建服务器svn以及前端开发环境

    1.需要在linux环境的服务器下搭建node和npm还有Grunt,所以先需要一个叫putty的工具连接服务器命令行终端 2.双击putty工具,在HostName一栏输入项目服务器地址:172.1 ...

  10. iOS 解决一个复杂bug 之 计分卡

    由于该模块界面和业务逻辑都很复杂,并且整个界面设计和业务逻辑都在ViewController(下面简称为VC)里面完成.该VC共有3000多行,一个函数几百张的也有.所以,解决起来真是头疼. 1. 问 ...