博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ThreadLocal原理以及使用
阅读量:2060 次
发布时间:2019-04-29

本文共 5530 字,大约阅读时间需要 18 分钟。

对threadLocal的实现的阅读

使用方式以及理解Set部分,包含创建

ThreadLocal
threadLocal = new ThreadLocal<>(); threadLocal.set(1);

1,threadLocal这种存储方式,内部只能存放一个引用(引用可以指向容器,比如map,或者integer这种单个的)

2,threadLocal的内部实现,是通过获取当前线程,再从当前线程中得到线程上存储的线程map ThreadLocalMap

ThreadLocalMap内部实现

1,ThreadLocalMap的key是当前实例化的threadLocal的地址,值是存进去的object类型

2,map内部的实现,是一个初始化为16,扩容阈值为16*0.75+1的entity数组
3,entity内部是一个reference(引用) +object的格式 reference是key,object是值

public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }

在set值的时候,会先去取得当前线程,以及线程内部存储的ThreadLocalMap格式的map,再往map里设值,如果当前线程还没有被其他的threadLocal给做过设值动作,map就没有被初始化,就先进行ThreadLocalMap初始化(类似hashMap)

private void set(ThreadLocal
key, Object value) { ... for (Entry e = tab[i]; 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); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }

在set中,会先通过计算key.hashcode&15的位置来取出一个合适的位置去放置当前线程的引用.

从这个位置开始往下遍历
如果值不为空,进去校验,如果当前entity的key==我的key,替换值
如果当前的entity不为空,但是entity内的引用已经是null,说明资源被释放了,通过replaceStaleEntry去进行设值操作
最后,如果找到一个空节点,创建新的entity,去放置这个k+v
并且判断是否需要进行rehash操作(扩容)

private void replaceStaleEntry(ThreadLocal
key, Object value, int staleSlot) { ... //slotToExpunge = 从当前staleSlot往前,寻找到最前面的那个k为null的index ... for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal
k = e.get(); //从当前节点staleSlot往下寻找,如果k==key,进行值覆盖后,再把key的位置放到数组的前面一点的位置上去(slotToExpunge) if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; //如果最前面的垃圾点位置和传入的位置是一样的,那么就可以把垃圾点位置更新到当前的i节点了,然后去进行垃圾节点清除的操作 if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } // If we didn't find stale entry on backward scan, the // first stale entry seen while scanning for key is the // first still present in the run. //如果往前没找到,那么slotToExpunge == staleSlot成立,那么slotToExpunge就会往后走,直到到达最后一个参数的时候,k还是null,这时候的slotToExpunge应该等于map.length if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } //如果循环完了,确定原先不存在了,就新增,新增的位置就是最开始找到的那个,"最前面"的空位 tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // If there are any other stale entries in run, expunge them //只要slotToExpunge!=staleSlot,那么要么是在staleSlot前面(之前循环得到的),要么是在staleSlot后面(通过 slotToExpunge = i得到的),就能判断出,当前数组中,还有垃圾数据,于是执行一次性清理的逻辑 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }
private int expungeStaleEntry(int staleSlot) {                      //释放内存            tab[staleSlot].value = null;            tab[staleSlot] = null;            size--;                       // 往下继续寻找,继续释放其他内存            Entry e;            int i;            for (i = nextIndex(staleSlot, len);     (e = tab[i]) != null;       i = nextIndex(i, len)) {                ThreadLocal
k = e.get(); if (k == null) { ... //释放内存 } else { int h = k.threadLocalHashCode & (len - 1); //如果当前k的hash值不等于i,意味着这个位置不是最合适的位置,所以先置空这个i //然后这个e还是持有的,通过找到hash所在位置,(等于是往回一些,从该位置开始,往后遍历,寻找到更贴近适合点的位置,这样做能够保证,这之前没有空档(null的槽) if (h != i) { tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } //返回最前面的一个为null的槽点 return i; }
private boolean cleanSomeSlots(int i, int n) {            boolean removed = false;            Entry[] tab = table;            int len = tab.length;            do {                i = nextIndex(i, len);                Entry e = tab[i];                if (e != null && e.get() == null) {                    n = len;                    removed = true;                    i = expungeStaleEntry(i);                }            } while ( (n >>>= 1) != 0);            return removed;        }

cleanSomeSlots

从刚才得到的最前面的一个槽点开始,往下遍历,只要有废弃的引用,就干掉
并且在过程中,再次进行校正,把i=expungeStaleEntry,这样多校准遍历几次
保证没有废弃引用的存在
然后返回给上层,是否remove过数据

另一个问题,内存泄露

为什么这里非要每次都及时的cleanSomeSlots
因为在这里,线程会去引用对象,而对象所属的threadlocal早就被释放了,那么这个对象其实应该不存在了,但是没有被及时释放

get逻辑

get逻辑比较简单,不贴代码了

主要是,get的时候,如果map存在,就去map里找,找到就返回,在找的过程中,如果发现废弃槽点,就进行清理
如果找不到,就返回null
如果map不存在,就设置默认值null进map,并且返回默认值
initialValue的方法为protected,可以被复写

转载地址:http://ttalf.baihongyu.com/

你可能感兴趣的文章
Leetcode C++《每日一题》20200626 338. 比特位计数
查看>>
Leetcode C++ 《拓扑排序-1》20200626 207.课程表
查看>>
Go语言学习Part1:包、变量和函数
查看>>
Go语言学习Part2:流程控制语句:for、if、else、switch 和 defer
查看>>
Go语言学习Part3:struct、slice和映射
查看>>
Go语言学习Part4-1:方法和接口
查看>>
Leetcode Go 《精选TOP面试题》20200628 69.x的平方根
查看>>
Leetcode C++ 剑指 Offer 09. 用两个栈实现队列
查看>>
Leetcode C++《每日一题》20200707 112. 路径总和
查看>>
云原生 第十一章 应用健康
查看>>
Leetcode C++ 《第202场周赛》
查看>>
云原生 第十二章 可观测性:监控与日志
查看>>
Leetcode C++ 《第203场周赛》
查看>>
云原生 第十三章 Kubernetes网络概念及策略控制
查看>>
《redis设计与实现》 第一部分:数据结构与对象 || 读书笔记
查看>>
《redis设计与实现》 第二部分(第9-11章):单机数据库的实现
查看>>
算法工程师 面经2019年5月
查看>>
搜索架构师 一面面经2019年6月
查看>>
稻草人手记
查看>>
第一次kaggle比赛 回顾篇
查看>>