ThreadLocal,即线程局部变量,用来为每一个使用它的线程维护一个独立的变量副本。这种变量只在线程的生命周期内有效。并且与锁机制那种以时间换取空间的做法不同,ThreadLocal没有任何锁机制,它以空间换取时间的方式保证变量的线程安全。

  本篇从源码方面分析ThreadLocal的实现原理。

  

  先看一下ThreadLocal类图结构

  

  SuppliedThreadLocal主要是JDK1.8用来扩展对Lambda表达式的支持,有兴趣的自行百度。

  ThreadLocalMap是ThreadLocal的静态内部类,也是实际保存变量的类。

  Entry是ThreadLocalMap的静态内部类。ThreadLocalMap持有一个Entry数组,以ThreadLocal为key,变量为value,封装一个Entry。

  

  下面以一张图简要说明Thread,ThreadLocal,ThreadLocalMap和Entry的关系。

  

  说明一下上图:

  1. 一个Thread拥有一个ThreadLocalMap对象
  2. ThreadLocalMap拥有一个Entry数组
  3. 每个Entry都有k--v
  4. Entry的key就是某个具体的ThreadLocal对象

  下面分析主要方法。

  1、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);
}

  这里可以看出:一个Thread只拥有一个ThreadLocalMap对象;具体存值调用的是ThreadLocalMap的set(),传入的参数key就是当前ThreadLocal对象。

  再看看ThreadLocalMap的set()方法:

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]; // 2
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); // 3
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold) // 4
rehash();
}
  1. 通过key的hashCode与数组容量 -1 取模,计算数组index
  2. 从当前index开始遍历,清除key为null的无效Entry
  3. 将K-V封装为Entry,并放入数组
  4. 判断是否需要进行Entry数组扩容。threshold的值为数组容量的2/3。

  看看扩容的resize()方法:

private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * ;
Entry[] newTab = new Entry[newLen];
int count = ; for (int j = ; 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 - );
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
} setThreshold(newLen);
size = count;
table = newTab;
}

  这里主要就是扩容为原先的2倍。然后遍历旧数组,根据新数组容量重新计算Entry在新数组中的位置。

  2、get()

  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();
}

  ThreadLocalMap的getEntry()方法如下:

private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - ); // 1
Entry e = table[i];
if (e != null && e.get() == key) // 2
return e;
else
return getEntryAfterMiss(key, i, e); //3
} private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length; while (e != null) { //4
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
  1. 计算index
  2. 当前index上的Entry不为空且key相同,直接返回
  3. 否则去相邻index寻找
  4. 循环查找,发现无效key就清除。找到就结束循环。

3、remove()

public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}

  处理方式和查找保存类似,删除对应Entry后都会去除key为null的无效元素。

注意

static class Entry extends WeakReference<ThreadLocal<?>> {}

  ThreadLocal可能存在OOM问题。因为ThreadLocalMap是使用ThreadLocal的弱引用作为key的,发生GC时,key被回收,这样我们就无法访问key为null的value元素,如果value本身是较大的对象,那么线程一直不结束的话,value就一直无法得到回收。特别是在我们使用线程池时,线程是复用的,不会杀死线程,这样ThreadLocal弱引用被回收时,value不会被回收。

  在使用ThreadLocal时,线程逻辑代码结束时,必须显示调用ThreadLocal.remove()方法。

线程局部变量ThreadLocal实现原理的更多相关文章

  1. 线程局部变量ThreadLocal的原理及使用范围_1

    线程局部变量ThreadLocal的原理及使用范围 使用原理 每个Thread中都有一个ThreadLocalMap成员, 该成员是ThreadLocal的内部类ThreadLocalMap类型.每使 ...

  2. ThreadLocal工作原理

    原文出处: imzoer 在这篇文章中,总结了一下面试过程中遇到的关于ThreadLocal的内容.总体上说,这样回答,面试算是过得去了.但是,这样的回答,明显仅仅是背会了答案,而没有去研究Threa ...

  3. java线程——线程局部变量

    一,线程局部变量ThreadLocal的作用 用于实现线程内部的数据共享,既对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,在另一个线程访问的时候,访问的由是另一份数据. 每个线程调用 ...

  4. Java基础教程——线程局部变量

    线程局部变量 ThreadLocal,线程局部变量,不提供锁,不做线程共享,而是为每个线程提供变量的独立副本. import java.util.concurrent.*; public class ...

  5. Java并发编程原理与实战二十五:ThreadLocal线程局部变量的使用和原理

    1.什么是ThreadLocal ThreadLocal顾名思义是线程局部变量.这种变量和普通的变量不同,这种变量在每个线程中通过get和set方法访问, 每个线程有自己独立的变量副本.线程局部变量不 ...

  6. 【java】ThreadLocal线程变量的实现原理和使用场景

    一.ThreadLocal线程变量的实现原理 1.ThreadLocal核心方法有这个几个 get().set(value).remove() 2.实现原理 ThreadLocal在每个线程都会创建一 ...

  7. python 线程中的局部变量ThreadLocal

    一个线程使用自己的局部变量比使用全局变量好局部变量只有线程自己能看见,不会影响其他线程全局变量的修改必须加锁 ThreadLocal 线程局部变量 import threading # 创建全局Thr ...

  8. ThreadLocal线程局部变量的使用

    ThreadLocal: 线程局部变量 一).ThreadLocal的引入 用途:是解决多线程间并发访问的方案,不是解决数据共享的方案. 特点:每个线程提供变量的独立副本,所有的线程使用同一个Thre ...

  9. 浅谈Flask 中的 线程局部变量 request 原理

    2017-11-27 17:25:11 晚橙 阅读数 600更多 分类专栏: Flask python 多线程   版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出 ...

随机推荐

  1. CF1019E Raining season

    https://www.luogu.org/problemnew/show/CF1019E 题解 \[ dis=day*a+b \] \[ b=-day*a+dis \] 然后就变成了斜率优化. 考虑 ...

  2. python分别使用多线程和多进程获取所有股票实时数据

    python分别使用多线程和多进程获取所有股票实时数据   前一天简单介绍了python怎样获取历史数据和实时分笔数据,那么如果要获取所有上市公司的实时分笔数据,应该怎么做呢? 肯定有人想的是,用一个 ...

  3. Gradel 多渠道打包 代码混淆

    http://blog.csdn.net/ttccaaa/article/details/47687241 http://www.bubuko.com/infodetail-987756.html h ...

  4. python使用__new__创建一个单例模式(单例对象)

    #单例模式:使一个类只产生一个对象.他们的id地址都指向同一个内存地址 第一步:理解谁创建了对象 # 单例模式# 首先明白,我们在创建一个类的对象的时候,其实是调用的这个类的父类,即继承object, ...

  5. Delphi XE2 之 FireMonkey 入门(14) - 滤镜: 概览

    相关单元: FMX.Filter FMX.FilterCatBlur FMX.FilterCatGeometry FMX.FilterCatTransition FMX_FilterCatColor ...

  6. python2.7+appium第一个脚本(使用夜神模拟器)

    搭建好环境后,可以开始准备脚本的编写工作 目录 1.安装夜神模拟器 2.使用uiautomatorviewer定位 3.运行第一个脚本 1.安装夜神模拟器 第一步:官网下载夜神模拟器,完成安装 双击下 ...

  7. Python 爬取淘宝商品数据挖掘分析实战

    Python 爬取淘宝商品数据挖掘分析实战 项目内容 本案例选择>> 商品类目:沙发: 数量:共100页  4400个商品: 筛选条件:天猫.销量从高到低.价格500元以上. 爬取淘宝商品 ...

  8. Python笔记(二十二)_魔法方法_基本魔法方法

    __init__(self[,...]) __init__和__new__组成python的构造器,但__init__更多的是负责初始化操作,相当于一个项目中的配置文件,__new__才是真正的构造函 ...

  9. TensorFlow学习笔记5-概率与信息论

    TensorFlow学习笔记5-概率与信息论 本笔记内容为"概率与信息论的基础知识".内容主要参考<Deep Learning>中文版. \(X\)表示训练集的设计矩阵 ...

  10. 工具 - VNC

    安装 ubuntu下vnc客户端的安装命令sudo apt-get install xtightvncviewer 重置密码 cd /root/.vnc/ rm -rf passwd vncserve ...