Java ThreadLocal

之前在写SSM项目的时候使用过一个叫PageHelper的插件

可以自动完成分页而不用手动写SQL limit

用起来大概是这样的

最开始的时候觉得很困惑,因为直接使用静态成员函数,那么就意味着如果有别的线程同时执行,可能会导致一些并发错误

答案是不会,因为PageHelper内部实现是使用到了ThreadLocal这个对象的,每个线程单独使用一个Page对象

百度了一下,发现ThreadLocal是一个提供类似线程内部的局部变量

我们来看一下ThreadLocal的源代码:初始化的时候涉及到这几个变量

  1. private static AtomicInteger nextHashCode =
  2. new AtomicInteger();
  3. private final int threadLocalHashCode = nextHashCode();
  4. private static final int HASH_INCREMENT = 0x61c88647;
  5. private static int nextHashCode() {
  6. return nextHashCode.getAndAdd(HASH_INCREMENT);
  7. }

每次创建一个ThreadLocal的时候就会给这个?ThreadLocal分配一个hashcode

为什么不是每次increateAndGet 注释里面有解释:

连续生成的散列码的区别

隐式顺序线程局部ID进入近最优分布

两个大小表的幂的乘法哈希值。

首先看一下get方法

  1. //ThreadLocal.java
  2. public T get() {
  3. //首先获取当前的Thread
  4. Thread t = Thread.currentThread();
  5. //通过当前Thread尝试获取 ThreadLocalMap
  6. ThreadLocalMap map = getMap(t);
  7. if (map != null) {
  8. //获取实体
  9. ThreadLocalMap.Entry e = map.getEntry(this);
  10. if (e != null) {
  11. @SuppressWarnings("unchecked")
  12. T result = (T)e.value;
  13. return result;
  14. }
  15. }
  16. //当前Thread并没有初始化map或者Thread值,进行初始化操作
  17. return setInitialValue();
  18. }

通过名字可以知道ThreadLocalMap似乎是个Map

我们先查看一下getMap

发现这个map是存储在Thread 里面的,包作用域,用户不可见

  1. //Tread.java class Thread
  2. ThreadLocal.ThreadLocalMap threadLocals = null;

我们现在看一下这个ThreadLocalMap的定义

  1. static class ThreadLocalMap {
  2. //定义键值对,是一个WeakReference
  3. static class Entry extends WeakReference<ThreadLocal<?>> {
  4. //ThreadLocal里面保存变量的值
  5. Object value;
  6. Entry(ThreadLocal<?> k, Object v) {
  7. super(k);
  8. value = v;
  9. }
  10. }
  11. //默认初始容量
  12. private static final int INITIAL_CAPACITY = 16;
  13. //桶
  14. private Entry[] table;
  15. private int size = 0;

初始化:

我们可以看到第一次初始化的时候是使用firstKey的threadLocalHashCode(firstKey指的是外部的this)刚才提到的初始化的时候分配的一个hashcode,具体桶的位置跟hashmap类似都是(桶-1)&hashcode

  1. //ThreadLocal.java
  2. //static class ThreadLocalMap
  3. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  4. table = new Entry[INITIAL_CAPACITY];
  5. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
  6. table[i] = new Entry(firstKey, firstValue);
  7. size = 1;
  8. setThreshold(INITIAL_CAPACITY);
  9. }

ThreadLocalMap里面的 get方法

  1. private Entry getEntry(ThreadLocal<?> key) {
  2. int i = key.threadLocalHashCode & (table.length - 1);
  3. Entry e = table[i];
  4. //如果找到了直接返回就好了
  5. if (e != null && e.get() == key)
  6. return e;
  7. else//如果没找到就执行下面的操作
  8. return getEntryAfterMiss(key, i, e);
  9. }

ThreadLocalMap解决hash冲突的方法并不是用链表,而是使用线性探测法

这也就解释了为什么分配的hashcode不应该是连续的原因,否则一旦出现hash冲突,线性探测找到一个可用的空间或者key对应的值非常艰难

我们来看一下是如何实现线性探测的:

  1. private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
  2. Entry[] tab = table;
  3. int len = tab.length;
  4. //当这个位置不是空的时候继续探测
  5. while (e != null) {
  6. ThreadLocal<?> k = e.get();
  7. //判断key是不是相等,如果相等说明找到了
  8. if (k == key)
  9. return e;
  10. //这里不太理解,查了一下别人的分析
  11. //k==null说明这个key已经被释放掉,需要清理掉
  12. if (k == null)
  13. expungeStaleEntry(i);
  14. else
  15. i = nextIndex(i, len);//((i + 1 < len) ? i + 1 : 0);就是下一个空间,如果到末尾就从头开始
  16. e = tab[i];//下一个空间里面的值
  17. }
  18. return null;
  19. }
  20. //这个函数不太理解
  21. //我猜大概意思就是需要别的地方有一个ThreadLocal引用否则ThreadLocal可能被清理掉
  22. //弱引用会被GC标记存活
  23. //这个做的应该是标记为null之后,把后面的值放到前面,否则再次get的时候碰到null就找不到了
  24. /**
  25. * 这个函数是ThreadLocal中核心清理函数,它做的事情很简单:
  26. * 就是从staleSlot开始遍历,将无效(弱引用指向对象被回收)清理,即对应entry中的value置为null,将指向这个entry的table[i]置为null,直到扫到空entry。
  27. * 另外,在过程中还会对非空的entry作rehash。
  28. * 可以说这个函数的作用就是从staleSlot开始清理连续段中的slot(断开强引用,rehash slot等)
  29. */
  30. private int expungeStaleEntry(int staleSlot) {
  31. Entry[] tab = table;
  32. int len = tab.length;
  33. // expunge entry at staleSlot
  34. //直接置null
  35. tab[staleSlot].value = null;
  36. tab[staleSlot] = null;
  37. size--;
  38. // Rehash until we encounter null
  39. Entry e;
  40. int i;
  41. //开始从这个位置开始线性探测
  42. for (i = nextIndex(staleSlot, len);
  43. (e = tab[i]) != null;
  44. i = nextIndex(i, len)) {
  45. //每次线性探测一个格子直到找到null
  46. ThreadLocal<?> k = e.get();
  47. //key为null说明需要清理掉
  48. if (k == null) {
  49. e.value = null;
  50. tab[i] = null;
  51. size--;
  52. } else {
  53. //重新找到标记桶的位置
  54. int h = k.threadLocalHashCode & (len - 1);
  55. if (h != i) {
  56. //清理当前位置
  57. tab[i] = null;
  58. // Unlike Knuth 6.4 Algorithm R, we must scan until
  59. // null because multiple entries could have been stale.
  60. //线性探测一个可以用的位置,然后把自己放进去
  61. while (tab[h] != null)
  62. h = nextIndex(h, len);
  63. tab[h] = e;
  64. }
  65. }
  66. }
  67. return i;
  68. }

并发安全问题:

代码中没有使用到任何锁和同步,为什么还是安全的

因为每个线程操作的ThreadLocalMap都是每个线程自带的,当然不用同步啦

具体使用:比如说解决SimpleDateFormat的问题

这样每个线程只会创建一个

具体的线性探测hash可以看着里面的图

https://www.cnblogs.com/micrari/p/6790229.html

Java ThreadLocal 源代码分析的更多相关文章

  1. Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6642463 在前面几篇文章中,我们详细介绍了A ...

  2. java TreeMap 源代码分析 平衡二叉树

    TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点. TreeSet 和 TreeMap 的关系 为了让大家了解 TreeMap 和 Tre ...

  3. 多线程之美2一ThreadLocal源代码分析

    目录结构 1.应用场景及作用 2.结构关系 2.1.三者关系类图 2.2.ThreadLocalMap结构图 2.3. 内存引用关系 2.4.存在内存泄漏原因 3.源码分析 3.1.重要代码片段 3. ...

  4. Java ConcurrentHashMap 源代码分析

    Java ConcurrentHashMap jdk1.8 之前用到过这个,但是一直不清楚原理,今天抽空看了一下代码 但是由于我一直在使用java8,试了半天,暂时还没复现过put死循环的bug 查了 ...

  5. Java HashMap 源代码分析

    Java HashMap jdk 1.8 Java8相对于java7来说HashMap变化比较大,在hash冲突严重的时候java7会退化为链表,Java8会退化为TreeMap 我们先来看一下类图: ...

  6. Java ArrayList 源代码分析

    Java ArrayList 之前曾经参考 数据结构与算法这本书写过ArrayList的demo,本来以为实现起来都差不多,今天抽空看了下jdk中的ArrayList的实现,差距还是很大啊 首先看一下 ...

  7. Android应用程序进程启动过程的源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址: http://blog.csdn.net/luoshengyang/article/details/6747696 Android 应用程序框架层创 ...

  8. java源代码分析----jvm.dll装载过程

    简述众所周知java.exe是java class文件的执行程序,但实际上java.exe程序只是一个执行的外壳,它会装载jvm.dll(windows下,以下皆以windows平台为例,linux下 ...

  9. Java源代码分析与生成

    源代码分析:可使用ANTLRANTLR是开源的语法分析器,可以用来构造自己的语言,或者对现有的语言进行语法分析. JavaParser 对Java代码进行分析 CodeModel 用于生成Java代码 ...

随机推荐

  1. SpringBoot 中解决跨域请求

    CORS 理解 同源策略是web浏览器实现的一个重要的安全概念,它防止JavaScript代码对不同的来源(例如,不同的域)发出请求,而不是它所服务的来源.虽然同源策略有效地防止来自不同来源的资源,但 ...

  2. Genymotion安卓模拟器和VirtualBox虚拟机安装、配置、测试(win7_64bit)

    1.概述 VirtualBox是一个优秀的虚拟机软件,它可以在电脑上提供另一个操作系统的运行环境,使多个系统同时运行.VirtualBox支持的操作系统包括Windows.Mac OS X.Linux ...

  3. 021.10 IO流 打印流

    内容:PrintStream:字节流    PrintWriter:字符流 PrintStream public static void main(String[] args) throws IOEx ...

  4. PHP版本解密openrtb中的价格

    Decrypt Price Confirmations 原文地址 https://developers.google.com/ad-exchange/rtb/response-guide/decryp ...

  5. Mac下安装Spark

    1.Scala 官网下载scala安装包后解压,路径随意. 编辑/etc/bash_profile添加$SCALA_HOME并修改相应PATH 2.SSH无密码登陆 ssh-keygen -t rsa ...

  6. 【NOI2008】假面舞会

    题目描述 一年一度的假面舞会又开始了,栋栋也兴致勃勃的参加了今年的舞会. 今年的面具都是主办方特别定制的.每个参加舞会的人都可以在入场时选择一 个自己喜欢的面具.每个面具都有一个编号,主办方会把此编号 ...

  7. MacBook搭建go语言开发环境

    mac下要安装 go 最简单的方式是通过 homebrew 直接执行: brew update && brew upgrade brew install go 安装完成后需要指定 GO ...

  8. VIM之模式

    1.模式介绍: 在真正开始使用VIM之前,你必须先了解VIM的模式,否则在 VIM 面前你可能会手足无措.VIM是有模式 编辑器,这意味着 VIM 有多种不同的工作模式,在不同的工作模式下用户相同的操 ...

  9. leetcode231 2的幂 leetcode342 4的幂 leetcode326 3的幂

    1.2的幂 正确写法: class Solution { public: bool isPowerOfTwo(int n) { ) return false; )) == ; } }; 错误写法1: ...

  10. mysql自增ID过大修改方法

    执行sql: alter table table_name AUTO_INCREMENT=100