从获取当前用户到Security再到ThreadLocal(二)

以前觉得鲁迅先生的“我家门口有两棵树一棵是枣树,另一棵也是枣树”完完全全是在凑字数。不过最近把这个句式一带入到“我的账户里有两只股票,一只是绿的,另一只也是绿的”,马上就懂了,不愧是大文豪,悲悯的氛围感一下就出来了。

上文介绍了security的用户信息保存在ThreadLocal中,那我们这篇文章聊下ThreadLocal

上节回顾

  上篇文章主要是从具体实现层面来分析,基于源码和调用逻辑的分析聚焦于‘术’,我们再从‘法’的层面,换个视角看下为什么要这么做,加深理解;
  我们部署的web应用,从tomcat——>security——>Ctrl——>Serveice——>Dao,我们需要用到用户信息的地方很多,如果换做你自己想法实现
1、最简单的就是透传,从头到尾的方法都加上User这个对象,需要时获取,这样做的缺点显而易见:

  • 你不知道哪些方法要使用用户信息,为了后面万一需要使用,得把所有方法的入参一股脑的加上
  • 写业务代码的人还要关注这个方法需不需要获取用户信息,导致所有接口入参冗余
  • 后期修改变更极为痛苦

2、统一存储,然后提供工具类获取,这种方法比上一个优雅,但是存在一个问题是多个线程访问同一个资源,存在竟态条件,会有线程安全问题,那就得加锁,上锁的话性能就不行了

3、如果可以将用户信息进行本地化存储,让每个线程都有属于自己的本地资源,避免多线程之间的共享。这样ThreadLocal就应运而生

ThreadLocal初介绍

我们先看Thread类

public class Thread implements Runnable {
    ···
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */(开始学英语了,与此线程相关的线程本地值。这map 是由ThreadLocal 类进行维护)
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.(与此线程相关的InheritableThreadLocal值。这map 是由InheritableThreadLocal 类进行维护)
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    ···

  可以看到ThreadLocalMap 是Thread类的一个属性;但是这些属性ThreadLocal有啥关系,怎么使用,我们先搁置,把后面看了再来解决


image.png

  可以看到ThreadLocalMap是ThreadLocal的内部类,然后该内部类里面又存在一个内部类Entry,这个Entry理念和HashMap的Entry类似;可以看到ThreadLocalMap里面把我们需要存储的东西放到Entry[]数组table里面,即我们的用户信息是放到一个对象数组里面的,最终存储在Object value这个属性中;

我把上面这段话再捋一捋,把整个过程翻译下:
我们保存用户信息的时候,在security里面是运行的代码是 contextHolder.set(context); contextHolder就是一个threadLocal 对象,这个然后contextHolder调用的set方法如下:

public void set(T value) {
       //获取到当前线程
       Thread t = Thread.currentThread();
       //根据线程获取当前线程的ThreadLocalMap对象
       ThreadLocalMap map = getMap(t);
       if (map != null)
           //设置进去的key为ThreadLocal; value为我们想要保存的用户数据
           map.set(this, value);
       else
           createMap(t, value);
   }
ThreadLocalMap getMap(Thread t) {
       return t.threadLocals;
   }
public T get() {
       //获取到当前线程
       Thread t = Thread.currentThread();
       //根据线程获取当前线程的ThreadLocalMap对象
       ThreadLocalMap map = getMap(t);
       if (map != null) {
            //this就是指代当前的这个threadlocal对象
           ThreadLocalMap.Entry e = map.getEntry(this);
           if (e != null) {
               @SuppressWarnings("unchecked")
               T result = (T)e.value;
               return result;
           }
       }
       //找不到的情况下,调用初始化方法,返回初始值
       return setInitialValue();
   }
ThreadLocalMap getMap(Thread t) {
       return t.threadLocals;
   }

ThreadLocal深入

弱引用&内存泄露

ThreadLocalMap源码图.png

  从图上可知,Entry是继承了弱引用的类,但是Entry本身不是弱引用,里面那个key才是,将key(即当前threadlocal对象)存在当前父类的referent属性中,value存放我们想要存储的值;


threadlocal的引用关系图.png(网图,侵删)
  • 什么情况下会存在内存泄露?
      拿线程池举例:线程池中的线程生命周期比较长,线程引用到线程对象的强引用链会一直存在,但是图片上面那条threadlocal的引用随着方法的出栈后,该条强引用链就没有了,这一时刻分析下threadlocal的
    的引用链路,只存在一条了,就是放入到Entry-key里面存在一条弱引用链,然后发生gc,由于key是弱引用,所以
    threadLocal 对象会被回收,这个时候Entry里面的key没了,没有key,那value肯定访问不到,这个时候Entry对象占用的内存区域无法被释放,造成内存泄露。
  • 那为啥要设计为弱引用而不是强引用呢?
      这个是没有明确答案的,只能自己揣摩设计思路,我的理解是threadlocal放入到Entry里面的key如果是强引用,使用完了,开发人员没有释放,会造成更大的内存泄露,两权相害取其轻,既然都会泄露,所以稍微选择影响小点的,所以改为弱引用;另外代码里面多处:set,扩容等都涉及清除掉Entry key为空的情况,反向印证了作者是知道这一点,且为之做了弥补措施

扩容

@TODO

Q & A

Q:为什么threadlocalMap要设计到Thread线程里面,而不是直接将threadlocal设计为一个公用的map,map-key为线程对象,map-value为我们想存储的值,岂不美哉?
A:这样多个线程访问threadlocalMap,就又回到集合下Map的思路了,会产生线程安全问题,同时加锁会导致性能损耗;


Q:为什么value不为弱引用?
A:Entry中的value如果弱引用,那么只要GC,value就会被清理掉,这时如果通过threadlocal对象查对应的值,是null;


Q:threadlocal有什么好处?
A:一是可以避免竞争,各自有一份副本;二是可以方便线程传参,比如登录用户信息;


Q:threadlocal使用场景?
A:保存数据库链接;如果一个请求中涉及多个 DAO 操作,而如果这些DAO中的Connection都是独立的话,就没有办法完成一个事务。但是如果DAO 中的 Connection 是从 ThreadLocal 中获得的(意味着都是同一个对象), 那么这些 DAO 就会被纳入到同一个 Connection 之下。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容