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. CodeForces 1198D 1199F Rectangle Painting 1

    Time limit 1000 ms Memory limit 262144 kB 解题思路 一堆循环嵌套的那种dp,不好想.但是可以搜啊,很暴力的.记忆化一下就好. 我们定义搜索函数\(\text{ ...

  2. 01 Netty是什么?

    01 Netty是什么? IO编程 我们简化下场景:客户端每隔两秒发送一个带有时间戳的 "hello world" 给服务端,服务端收到之后打印. 为了方便演示,下面例子中,服务端 ...

  3. DC.p4: programming the forwarding plane of a data-center switch

    Name of article:Dc. p4: Programming the forwarding plane of a data-center switch Origin of the artic ...

  4. mysql分组,行转列

    aaarticlea/jpeg;base64,/9j/4QEsRXhpZgAASUkqAAgAAAAPAJqCCgABAAAAwgAAABABAgAQAAAAygAAAAABAwABAAAAQBAAA

  5. Laravel 在homestead 平台上命令

    使用以下命令查看 Heroku 站点地址: $ heroku domains

  6. 移动端调试 — iPhone +Safari

    1   开启iOS设备的调试功能 打开“设置”程序,进入“Safari”->“高级”页面开启“Web检查器”: 2   mac safari浏览器设置开发者工具 safari ->偏好设置 ...

  7. python值的引用传递和go语言的值传递

    一:值传递 实参a 原本指向地址 1638212,代表1638212这个地址的值是3.在swap函数中,实参a将值拷贝给形参a,形参a此时也在内存中拥有地址,地址= xxxx,值为3,在所有的函数体内 ...

  8. Android studio 不能创建Activity等文件

    这是我之前安装Android studio的一系列问题:http://tieba.baidu.com/p/5921373177 1. 不能创建Activity等许多文件: 2. 工程运行不了: 3. ...

  9. django中间件(获取请求ip)

    def simple_middleware(get_response): # 此处编写的代码仅在Django第一次配置和初始化的时候执行一次. print('1----django启动了') def ...

  10. Golang 调用 aws-sdk 操作 S3对象存储

    Golang 调用 aws-sdk 操作 S3对象存储 前言 因为业务问题,要写一个S3对象存储管理代码,由于一直写Go,所以这次采用了Go,Go嘛,快,自带多线程,这种好处就不用多说了吧. 基础的功 ...