zoukankan      html  css  js  c++  java
  • ThreadLocal,Thread和ThreadLocalMap分享

    简介

    ThreadLocal的用处

    ThreadLocal是为了将数据记录一份到某个线程里,确保该数据线程安全

    例如数据库的Connection放入ThreadLocal,一个事务会用到很多DAO,但只能用共同的Connection,这样才能保证事务完整性

    所以当某个类的其中一个变量,会被同一个线程多次使用,并且还严格的规定每次都得是这个变量操作

    那么就能把这个变量放入ThreadLocal

    Spring也是将各种Bean放入ThreadLocal中来确保Bean的“无状态”化

    别被误导了

    今天翻书看了关于ThreadLocal的介绍,和网上一些关于ThreadLocal的博客,这个原理介绍真的是坑,大错特错

    大家可能都看到过下面这种所谓的ThreadLocal简单的实现思路介绍:

    完了后还加上一句:

    虽然上面的代码清单中的这个ThreadLocal实现版本显得比较简单粗爆,但其目的主要在与呈现JDK中所提供的ThreadLocal类在实现上的思路

    上面这样简化ThreadLocal实现根本错的离谱

    不仅是有的博客这样,包括书本也是这样介绍的,传播知识给他人,的确可以简化代码实现,但不等于更改了正确的实现思路!

    这样会误导他人对ThreadLocal的进一步学习

    ThreadLocal真正的实现方式

    先说总结,跟上面错误做对比

    1.ThreadLocalMap 别看有个Map结尾,其实压根就是重新实现的类

       跟Map没半毛钱关系,没实现Map接口的,没用HashMap,别觉得根据key找value就只能使用map了

    2.线程根据key找对应的value,这个key并不是线程id,而是ThreadLocal类

       为什么,因为ThreadLocalMap是存放在线程里的,每个线程都只有一个只属于自己的ThreadLocalMap

       这样的话存个毛的线程id,有什么意义?

    揭开Thread,ThreadLocal,ThreadLocalMap真正的关系

    首先进入ThreadLocal,发现如下

     ThreadLocalMap实在ThreadLocal里实现的

     直接找到ThreadLocal的set()方法

    1     public void set(T value) {
    2         Thread t = Thread.currentThread();  //获得当前线程
    3         ThreadLocalMap map = getMap(t);   //将当前线程作为参数传入,来获取ThreadLocalMap
    4         if (map != null)
    5             map.set(this, value);
    6         else
    7             createMap(t, value);
    8     }

    然后进入getMap()方法

    1     ThreadLocalMap getMap(Thread t) {
    2         return t.threadLocals;        //追踪后发现t.threadLocals就是 ThreadLocal.ThreadLocalMap threadLocals;
    3     }

    如果得到的map为null,那么说明是第一次,走createMap方法创建

    1  void createMap(Thread t, T firstValue) {
    2         t.threadLocals = new ThreadLocalMap(this, firstValue);  //看到了吧,ThreadLocalMap直接是给Thread保存的
    3     }

    进入new ThreadLocalMap方法,这里注意,传入的this就是指ThreadLocal

    1  ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {   //看到参数名字没,firstKey,ThreadLocal传进来是当作key值的!
    2             table = new Entry[INITIAL_CAPACITY];                 //table其实是private Entry[] table; 一个数组而已
    3             int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    4             table[i] = new Entry(firstKey, firstValue);
    5             size = 1;
    6             setThreshold(INITIAL_CAPACITY);
    7         }

    ThreadLocalMap没有实现Map接口,跟Map没半毛钱关系,至于Entry是什么,我们看看

    1 static class Entry extends WeakReference<ThreadLocal<?>> {
    2             /** The value associated with this ThreadLocal. */
    3             Object value;
    4 
    5             Entry(ThreadLocal<?> k, Object v) {
    6                 super(k);
    7                 value = v;
    8             }
    9         }

    首先WeakReference是指弱引用的意思,继承了这玩意有以下效果:

    当一个对象仅仅被weak reference(弱引用)指向, 而没有任何其他strong reference(强引用)指向的时候, 如果这时GC运行, 那么这个对象就会被回收,不论当前的内存空间是否足够,这个对象都会被回收。

    好处在于,如果某个ThreadLocal被回收了,那么ThreadLocalMap的这个Key在下次GC()的时候也会被回收(不然就造成内存泄露了,这个key永远不会被调用)

    Entry是一个ThreadLocalMap的内部类,跟Map的Entry实现有点像,有key和value的定义(俗称 桶)

    ok,我们发现,ThreadLocalMap创建后,就是初始化一个Entry的数组,生成一个Entry将ThreadLocal作为key值,将要存的值作为value放入其中存好

    那么假设现在ThreadLocalMap已经存在,走的是第一个分支,直接set,我们看看他怎么实现的

     1    private void set(ThreadLocal<?> key, Object value) {
     2 
     3             Entry[] tab = table;
     4             int len = tab.length;
     5             int i = key.threadLocalHashCode & (len-1);
     6 
     7             for (Entry e = tab[i];
     8                  e != null;
     9                  e = tab[i = nextIndex(i, len)]) {
    10                 ThreadLocal<?> k = e.get();
    11 
    12                 if (k == key) {
    13                     e.value = value;
    14                     return;
    15                 }
    16 
    17                 if (k == null) {
    18                     replaceStaleEntry(key, value, i);
    19                     return;
    20                 }
    21             }
    22 
    23             tab[i] = new Entry(key, value);
    24             int sz = ++size;
    25             if (!cleanSomeSlots(i, sz) && sz >= threshold)
    26                 rehash();
    27         }

    看到第7行的for没有,直接遍历方才说的Entry数组,将ThreadLocal取出来比较(相当于key比较),匹配就设置value,没这个key就存起来

    ThreadLocalMap为啥不用HashMap而是自己数组实现

    有key和value这个概念出现,也不是一定要用HashMap这些的,为什么用数组

    个人觉得是省开销,创建Map对象的开销和使用Map的开销,毕竟ThreadLocalMap初始默认长度为16,而真实情况中一个线程不会有这么本地变量要保存

    所以,当使用ThreadLocal来存的时候,ThreadLocal会创建一个ThreadLocalMap给调用它的线程,自己作为key值去保存

    取值的时候,ThreadLocal拿Thread里保存的ThreadLocal,然后将自身作为key值去取值

    为什么ThreadLocalMap的代码不放在Thread中

    网上有个答案我觉得很合理:

    将ThreadLocalMap定义在Thread类内部看起来更符合逻辑
    但是ThreadLocalMap并不需要Thread对象来操作,所以定义在Thread类内只会增加一些不必要的开销。
    定义在ThreadLocal类中的原因是ThreadLocal类负责ThreadLocalMap的创建和使用
    总的来说就是,ThreadLocalMap不是必需品,定义在Thread中增加了成本,定义在ThreadLocal中按需创建。

    线程不一定都用到ThreadLocal的哦,如果不使用,就不会被赋值一个ThreadLocalMap

    换个思维,这其实也是种不错的设计模式:

    一个工具类把自身当作唯一标识,去操作工具类本身的方法,只需要将数据记录给调用它的类就好

    ThreadLocal的内存泄露问题

    正常来说,我们创建一个线程,跑完后会销毁,自动调用ThreadLocal的remove()方法,清除ThreadLocalMap的内容

    但是,实际中我们是使用线程池的,而线程跑完后会返回线程池中,并不会销毁

    这时候的ThreadLocalMap的内容就还在的(内存就是这里泄露啦)

    所以,在线程池中用ThreadLocal,记得run()要跑完时用下remove()方法清除ThreadLocalMap中的key

    至此分享完毕啦,希望大家点点赞,或者一键3连不迷路~~

    学习和点赞一样,可不能下次一定啊! 感谢你的三连~
  • 相关阅读:
    整数m去掉n位后剩下最大(小)值
    蛇形矩阵(二)
    Kibana源码启动报错记录--ENOSPC
    Kibana问题记录:yarn test 运行报错 error Trailing spaces not allowed no-trailing-spaces
    Vim 编辑器中全选操作
    Ubuntu系统中连接TFS的Git地址注意事项
    Ubuntu系统升级遇到问题记录
    Kibana6.2.x 插件理解
    Kibana问题搜集---下载源码,执行npm install 报错
    Kibana6.2.2源码入口
  • 原文地址:https://www.cnblogs.com/top-housekeeper/p/14116446.html
Copyright © 2011-2022 走看看