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. 计蒜客 T2237 魔法 分类讨论

    Code: #include<bits/stdc++.h> #define setIO(s) freopen(s".in","r",stdin) # ...

  2. Windows上安装Apache

    1.下载 (1)进入Apache官网http://httpd.apache.org— (2)点击Download (3)点击Files for Microsoft Windows (4)点击Apach ...

  3. oracle各服务说明及cmd启动启动命令

    成功安装Oracle 11g后,共有7个服务,一.这七个服务的含义分别为:1. Oracle ORCL VSS Writer Service:Oracle卷映射拷贝写入服务,VSS(Volume Sh ...

  4. Linux shell】grep命令精确匹配字符串查找

    需求: 精确匹配查找某个字符串   精确匹配: 例如: 在抽取字符串“48”,返回结果包含诸如484和483等包含“48”的其他字符串,实际上应精确抽取只包含48的各行. 使用grep抽取精确匹配的一 ...

  5. 安装完Fedora 18后需要做的事情

    折腾了好久,在网上查看了好多资料,总算吧安装好的Fedora 18配置得差不多了,现在将过程记录下来,供以后查看用,同时也许还能帮助到和我遇到同一问题的朋友们,以后再有什么再继续添加吧. 一.添加 y ...

  6. 如何:执行大型 XML 文档的流式转换 大XML文件解析入库的一个方法

    w Parsing Huge XML Files Incrementally http://pclib.github.io/safari/program/python-cookbook/Text/ch ...

  7. vim推荐的光标移动配置文件?

    http://roclinux.cn/?p=1466 inoremap jk inoremap ... 参考较好的vim设置文件 : 共享粘贴板: set clipboard+=unnamed 除了映 ...

  8. Vue音乐播放器(三):项目目录介绍,以及图标字体、公共样式等资源准备

    我们所有的开发都是基于修改src下面的目录 里面的文件去做开发即可 stylus的使用是需要下载stylus-loader的包的 渲染效果 配置修改eslintrc.js 配置了webpack.bas ...

  9. Delphi XE2 之 FireMonkey 入门(9) - TBitmap

    TBitmap 主要成员: { 方法 } SetSize();              //设置大小 Clear();                //取消, 就是用指定颜色覆盖 ClearRec ...

  10. charles之抓取浏览器https请求

    用charles抓取浏览器https的包时,请求显示为unknown,且请求和响应数据乱码,本篇介绍如何抓取正常响应的https请求 目录 1.安装charles 2.安装证书.添加域名 3.抓包 1 ...