ThreadLocal

Java篇

  1. 是什么
  2. 怎么用
  3. 源码
  4. 缺点
  5. 总结

是什么

ThreadLocal是一个关于创建线程局部变量的类,这个变量只能当前线程使用,其他线程不可用。

ThreadLocal提供get()和set()方法创建和修改变量。

怎么使用

  1. ThreadLocal threadLocal = new ThreadLocal();
  1. ThreadLocal<String> threadLocal = new ThreadLocal<>();
  1. ThreadLocal threadLocal = new ThreadLocal<String>() {
  2. @Override
  3. protected String initialValue() {
  4. return "初始化值";
  5. }
  6. };

源码

类结构图

get(),set()

查看ThreadLocal中的get(),set()中有一个ThreadLocalMap对象

  1. //set 方法
  2. public void set(T value) {
  3. Thread t = Thread.currentThread();
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null)
  6. map.set(this, value);
  7. else
  8. createMap(t, value);
  9. }
  10. //get方法
  11. public T get() {
  12. Thread t = Thread.currentThread();
  13. ThreadLocalMap map = getMap(t);
  14. if (map != null) {
  15. ThreadLocalMap.Entry e = map.getEntry(this);
  16. if (e != null) {
  17. @SuppressWarnings("unchecked")
  18. T result = (T)e.value;
  19. return result;
  20. }
  21. }
  22. return setInitialValue();
  23. }

ThreadLocalMap

ThreadLocalMap 就是一个内部静态类,没有继承也没有接口,是一个自定义的Hash映射,用户维护线程局部变量。

  1. static class ThreadLocalMap

ThreadLocalMap的内部类Entry,继承WeakReference 弱引用

  1. static class Entry extends WeakReference<ThreadLocal<?>> {
  2. Object value;
  3. Entry(ThreadLocal<?> k, Object v) {
  4. //key放在WeakReference<ThreadLocal<?>>中
  5. super(k);
  6. //变量放在Object value中
  7. value = v;
  8. }
  9. }

ThreadLocalMap中存放线程局部变量的数据结构

  1. private Entry[] table;

小结:

  1. ThreadLocal ——> ThreadLocalMap——> Entry[]
  2. Entry维护一个ThreadLocal 作为key,value对应ThreadLocal的值

初始化方法

  1. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  2. //默认容量为16
  3. table = new Entry[INITIAL_CAPACITY];
  4. //threadLocalHashCode是一个原子类AtomicInteger的实例,每次调用会增加0x61c88647。&位移操作使存放分布均匀
  5. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
  6. //放入数组
  7. table[i] = new Entry(firstKey, firstValue);
  8. size = 1;
  9. setThreshold(INITIAL_CAPACITY);
  10. }
  11. //nextHashCode实现
  12. private final int threadLocalHashCode = nextHashCode();
  13. private static AtomicInteger nextHashCode =
  14. new AtomicInteger();
  15. private static final int HASH_INCREMENT = 0x61c88647;
  16. private static int nextHashCode() {
  17. return nextHashCode.getAndAdd(HASH_INCREMENT);
  18. }

小结:

  1. ThreadLocalMap默认容量为16,每次计算索引位置会加0x61c88647然后和长度-1取模
  2. 索引是原子类

Entry的get

  1. private Entry getEntry(ThreadLocal<?> key) {
  2. //定位i的位置
  3. int i = key.threadLocalHashCode & (table.length - 1);
  4. Entry e = table[i];
  5. if (e != null && e.get() == key)
  6. return e;
  7. else
  8. return getEntryAfterMiss(key, i, e);
  9. }
  10. private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
  11. Entry[] tab = table;
  12. int len = tab.length;
  13. //hashcode索引相同所以查找下一个,用循环比对取出
  14. while (e != null) {
  15. ThreadLocal<?> k = e.get();
  16. if (k == key)
  17. return e;
  18. if (k == null)
  19. expungeStaleEntry(i);
  20. else
  21. i = nextIndex(i, len);
  22. e = tab[i];
  23. }
  24. return null;
  25. }

小结:

  1. get方法中先计算索引位置,如果key相同则返回,不同则用线性探测法取出,当key为null的时候清理i所在位置直到不为null的数据。如果找不到key的数据则返回null

Entry的Set

  1. private void set(ThreadLocal<?> key, Object value) {
  2. Entry[] tab = table;
  3. int len = tab.length;
  4. //hashcode索引
  5. int i = key.threadLocalHashCode & (len-1);
  6. //线性探测法,如果在有值的情况下,key不同则继续下一个
  7. for (Entry e = tab[i];
  8. e != null;
  9. e = tab[i = nextIndex(i, len)]) {
  10. ThreadLocal<?> k = e.get();
  11. //如果当前有值&&key相同则更新value
  12. if (k == key) {
  13. e.value = value;
  14. return;
  15. }
  16. //如果key空,则key-value重新替换
  17. if (k == null) {
  18. replaceStaleEntry(key, value, i);
  19. return;
  20. }
  21. }
  22. //索引位置找到,插入key-value,对size+1
  23. tab[i] = new Entry(key, value);
  24. int sz = ++size;
  25. //cleanSomeSlots清理key关联的对象被回收的数据,如果没有被清理的&&size大于扩容因子,刷新
  26. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  27. rehash();
  28. }

小结:

1.计算索引位置

2.如果当前位置有值则索引+1判断是否为空,不为空继续+1,直到找到位置插入

3.size+1

4.是否清理key为null的数据,如果没有被清理&& size大于列表长度的2/3则扩容

清理key关联的对象被回收的数据

  1. private boolean cleanSomeSlots(int i, int n) {
  2. boolean removed = false;
  3. Entry[] tab = table;
  4. int len = tab.length;
  5. do {
  6. i = nextIndex(i, len);
  7. Entry e = tab[i];
  8. //key为null,被清理
  9. if (e != null && e.get() == null) {
  10. n = len;
  11. removed = true;
  12. //移除i位置之后为key为null的元素
  13. i = expungeStaleEntry(i);
  14. }
  15. } while ( (n >>>= 1) != 0);
  16. return removed;
  17. }

expungeStaleEntry方法

  1. private int expungeStaleEntry(int staleSlot) {
  2. Entry[] tab = table;
  3. int len = tab.length;
  4. //将上面staleSlot的数据清空,大小减去1
  5. tab[staleSlot].value = null;
  6. tab[staleSlot] = null;
  7. size--;
  8. Entry e;
  9. int i;
  10. //以staleSlot往后找key为null的
  11. for (i = nextIndex(staleSlot, len);
  12. (e = tab[i]) != null;
  13. i = nextIndex(i, len)) {
  14. ThreadLocal<?> k = e.get();
  15. //key为null清空
  16. if (k == null) {
  17. e.value = null;
  18. tab[i] = null;
  19. size--;
  20. } else {
  21. //key不为null,计算当前hashCode索引位置,如果不相同则把当前i清除,当前h位置不为null,再向后查找key合适的索引
  22. int h = k.threadLocalHashCode & (len - 1);
  23. if (h != i) {
  24. tab[i] = null;
  25. while (tab[h] != null)
  26. h = nextIndex(h, len);
  27. tab[h] = e;
  28. }
  29. }
  30. }
  31. return i;
  32. }

小结:

  1. 从staleSlot开始,清除key为null的Entry,并将不为空的元素放到合适的位置,最后遍历到Entry为空的元素时,跳出循环返回当前索引位置

rehash方法

  1. private void rehash() {
  2. expungeStaleEntries(); //调用expungeStaleEntries()方法
  3. //size的长度超过容量的3/4,则扩容
  4. if (size >= threshold - threshold / 4)
  5. resize();
  6. }
  7. private void resize() {
  8. Entry[] oldTab = table;
  9. int oldLen = oldTab.length;
  10. int newLen = oldLen * 2;
  11. Entry[] newTab = new Entry[newLen];
  12. int count = 0;
  13. for (int j = 0; j < oldLen; ++j) {
  14. Entry e = oldTab[j];
  15. if (e != null) {
  16. ThreadLocal<?> k = e.get();
  17. //key为null,value也设置为null,清理
  18. if (k == null) {
  19. e.value = null; // Help the GC
  20. } else {
  21. //重新设置元素位置
  22. int h = k.threadLocalHashCode & (newLen - 1);
  23. while (newTab[h] != null)
  24. h = nextIndex(h, newLen);
  25. newTab[h] = e;
  26. count++;
  27. }
  28. }
  29. }
  30. //设置阈值
  31. setThreshold(newLen);
  32. size = count;
  33. table = newTab;
  34. }
  35. private void expungeStaleEntries() {
  36. Entry[] tab = table;
  37. int len = tab.length;
  38. for (int j = 0; j < len; j++) {
  39. Entry e = tab[j];
  40. if (e != null && e.get() == null)
  41. expungeStaleEntry(j);
  42. }
  43. }

小结:

  1. 调用expungeStaleEntries方法,清理整个table中key为null的Entry
  2. 如果清理后size超过阈值的1/2,则进行扩容。
  3. 新表长度为老表2倍,创建新表。
  4. 遍历老表所有元素,如果key为null,将value清空;否则通过hash code计算新表的索引位置h,如果h已经有元素,则调用nextIndex方法直到寻找到空位置,将元素放在新表的对应位置。
  5. 设置新表扩容的阈值、更新size、table指向新表

缺点

内存泄露

从Entry源码中可以看出,Entry继承了WeakReference弱引用,如果外部没有引用ThreadLocal,则Entry中作为Key的ThreadLocal会被销毁成为null,那么它所对应的value不会被访问到。当线程一直在执行&&没有进行remove,rehash等操作时,value会一直存在内存,从而造成内存泄露

总结

  1. Thread中都有一个ThreadLocalMap
  2. ThreadLocalMap的key是ThreadLocal实例
  3. 默认容量大小为16,当size超过2/3容量&&没被清理就rehash,
  4. 当size超过扩容因子3/4的时候扩容为原来的2倍
  5. 当发现一个key为null的时候,会进行清理,直到下一个key不为null
  6. has冲突的解决方法和hashMap不相同,ThreadLocal是找这个冲突索引的下一个元素直到找到,hashMap是转换为红黑树

干了这杯java之ThreadLocal的更多相关文章

  1. 干了这杯Java之LinkedList

    LinkedList和ArrayList一样实现了List接口 ArrayList内部为数组 LinkedList内外为双向链表 实现了Deque接口,双端列队的实现 图片来自Wiki 内部实现为No ...

  2. 干了这杯Java之集合概览

    Java集合框架支持两种类型容器: 一种是为了存储一个元素的合集,为Collection 一种是为了存储键/值对,为Mapping Collection包含 Set存储不重复的元素 List存储一个有 ...

  3. 干了这杯Java,让你的Idea比eclipse好用

    1.Idea基本配置 1.1 Idea简介 Idea是一个专门针对Java的集成开发工具(IDE),由Java语言编写.所以,需要有JRE运行环境并配置好环境变量.简单的说,Idea是写代码用的工具. ...

  4. 干了这杯Java之ArrayList

    List存储一个有序元素合集 List接口的实现类有: ArrayList,LinkedList,Vector,Stack ArrayList一个数组型的List 默认容量为10 private st ...

  5. 干了这杯Java之Vector

    Vector实现了AbstractList抽象类和List接口,和ArrayList一样是基于Array存储的 Vector 是线程安全的,在大多数方法上存在synchronized关键字 //Vec ...

  6. 干了这杯Java之HashMap

    类: public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneab ...

  7. 干了这杯Java之transient关键字

    看源码的时候,发现transient这个关键字,不甚理解,查找资料发现:不被序列化 疑问: 静态变量是不是不被序列化? public class User implements Serializabl ...

  8. Java中ThreadLocal的设计与使用

    早在Java 1.2推出之时,Java平台中就引入了一个新的支持:java.lang.ThreadLocal,给我们在编写多线程程序时提供了一种新的选择.使用这个工具类可以很简洁地编写出优美的多线程程 ...

  9. 蓝桥杯java试题《洗牌》

    问题描述 小弱T在闲暇的时候会和室友打扑克,输的人就要负责洗牌.虽然小弱T不怎么会洗牌,但是他却总是输. 渐渐地小弱T发现了一个规律:只要自己洗牌,自己就一定会输.所以小弱T认为自己洗牌不够均匀,就独 ...

随机推荐

  1. python设计模式---结构型之门面模式

    门面,系统,客户端~ from django.test import TestCase class Hotelier: def __init__(self): print('Arranging the ...

  2. Linux 查看负载内存

    负载   内存     1.作用 top命令用来显示执行中的程序进程,使用权限是所有用户. 2.格式 top [-] [d delay] [q] [c] [S] [s] [i] [n] 3.主要参数 ...

  3. [C#.Net]全局钩子实现USB扫码枪无焦点状态下扫入

    https://www.cnblogs.com/masonlu/p/10105135.html

  4. 常用的js正则验证整理

    一.校验数字的js正则表达式 1 数字:^[0-9]*$ 2 n位的数字:^\d{n}$ 3 至少n位的数字:^\d{n,}$ 4 m-n位的数字:^\d{m,n}$ 5 零和非零开头的数字:^(0| ...

  5. AtCoder Grand Contest 030 (AGC030) C - Coloring Torus 构造

    原文链接https://www.cnblogs.com/zhouzhendong/p/AGC030C.html 题解 才发现当时是被题意杀了. 当时理解的题意是“对于任意的 (i,j) ,颜色 i 和 ...

  6. 20172328 2018-2019《Java软件结构与数据结构》第八周学习总结

    20172328 2018-2019<Java软件结构与数据结构>第八周学习总结 概述 Generalization 本周学习了二叉树的另一种有序扩展?是什么呢?你猜对了!ヾ(◍°∇°◍) ...

  7. 【Redis】-- 安装及配置

    我们redis的安装较为复杂,属于Linux上的源码编译安装,即不能直接通过yum安装. 1.安装Redis 具体步骤: 1.进入redis官网,复制下载链接,通过wget下载源码 官网:https: ...

  8. vscode断点调试工程化客户端文件

    一.调试webpack配置文件 launch.json的配置如下,在webpack.dev.config.js文件中设置断点,开始调试. { "version": "0. ...

  9. js 把 json 转为以 ‘&’ 连接的字符串

    /** * URL编码; * @param {参数} param */ export function toParams(param) { var result = "" for ...

  10. svn没有权限报出的错