在Java1.4之前,ThreadLocals会导致线程之间发生竞争。在新的设计里,每一个线程都有他们自己的ThreadLocalMap,用来提高吞吐量,然而,我们仍然面临内存泄漏的可能性,因为长时间运行线程的ThreadLocalMap中的值不会被清除

在Java的早期版本中,ThreadLocals在多个线程进行访问的时候存在竞争问题,使得它们在多核应用程序中几乎无用。在Java 1.4中,引入了一个新的设计,设计者把ThreadLocals直接存储在Thread中。当我们现在调用ThreadLocal的get方法时,将会返回一个当前线程里的实例ThreadLocalMap(ThreadLocal的一个内部类)

当一个线程退出时,它会删除它ThreadLocal里的所有值。这发生在exit()方法中,垃圾回收之前,如果我们在使用ThreadLocal后忘记调用remove()方法,那么当线程退出后值还会存在。

ThreadLocalMap包含了对ThreadLocal的弱引用以及值的强引用,但是,它并不会判断ReferenceQueue里面哪些弱引用的值已经被清除,因为Entry不可能立即从ThreadLocalMap中清除。

从线程Thread的角度来看,每个线程内部都会持有一个对ThreadLocalMap实例的引用,ThreadLocalMap实例相当于线程的局部变量空间,存储着线程各自的数据,具体如下:

Entry

Entry继承自WeakReference类,是存储线程私有变量的数据结构。ThreadLocal实例作为引用,意味着如果ThreadLocal实例为null,就可以从table中删除对应的Entry。

class Entry extends WeakReference>{

Object value;

Entry(ThreadLocal> k, Object v) {

super(k);

value = v;

}

}

复制代码

ThreadLocalMap

内部使用table数组存储Entry,默认大小INITIAL_CAPACITY(16),先介绍几个参数:

size:table中元素的数量。

threshold:table大小的2/3,当size >= threshold时,遍历table并删除key为null的元素,

如果删除后size >= threshold*3/4时,需要对table进行扩容。

ThreadLocal.set() 实现

public void set(T value){

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

}

ThreadLocalMap getMap(Thread t){

return t.threadLocals;

}

复制代码

从上面代码中看出来:

从当前线程Thread中获取ThreadLocalMap实例。

ThreadLocal实例和value封装成Entry。

接下去看看Entry存入table数组如何实现的:

private void set(ThreadLocal> key, Object value){

Entry[] tab = table;

int len = tab.length;

int i = key.threadLocalHashCode & (len-1);

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();

}

复制代码

1.通过ThreadLocal的nextHashCode方法生成hash值。

private static AtomicInteger nextHashCode = new AtomicInteger();

private static int nextHashCode(){

return nextHashCode.getAndAdd(HASH_INCREMENT);

}

复制代码

从nextHashCode方法可以看出,ThreadLocal每实例化一次,其hash值就原子增加HASH_INCREMENT。

2.通过 hash & (len -1) 定位到table的位置i,假设table中i位置的元素为f。

3.如果f != null,假设f中的引用为k:

如果k和当前ThreadLocal实例一致,则修改value值,返回。

如果k为null,说明这个f已经是stale(陈旧的)的元素。调用replaceStaleEntry方法删除table中所有陈旧的元素(即entry的引用为null)并插入新元素,返回。

否则通过nextIndex方法找到下一个元素f,继续进行步骤3。

如果f == null,则把Entry加入到table的i位置中。

通过cleanSomeSlots删除陈旧的元素,如果table中没有元素删除,需判断当前情况下是否要进行扩容。

4.如果f == null,则把Entry加入到table的i位置中。

5.通过cleanSomeSlots删除陈旧的元素,如果table中没有元素删除,需判断当前情况下是否要进行扩容。

table扩容

如果table中的元素数量达到阈值threshold的3/4,会进行扩容操作,过程很简单:

private void resize(){

//旧数组

Entry[] oldTab = table;

//旧数组长度

int oldLen = oldTab.length;

//新数组长度 = 旧数组长度*2

int newLen = oldLen * 2;

//新数组

Entry[] newTab = new Entry[newLen];

//计数

int count = 0;

for (int j = 0; j < oldLen; ++j) {

Entry e = oldTab[j];

if (e != null) {

ThreadLocal> k = e.get();

if (k == null) {

e.value = null; // Help the GC

} else {

int h = k.threadLocalHashCode & (newLen - 1);

while (newTab[h] != null)

h = nextIndex(h, newLen);

newTab[h] = e;

count++;

}

}

}

setThreshold(newLen);

size = count;

table = newTab;

}

复制代码

1.新建新的数组newTab,大小为原来的2倍。

2.复制table的元素到newTab,忽略陈旧的元素,假设table中的元素e需要复制到newTab的i位置,如果i位置存在元素,则找下一个空位置进行插入。

ThreadLocal.get() 实现

public T get(){

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings("unchecked")

T result = (T)e.value;

return result;

}

}

return setInitialValue();

}

private Entry getEntry(ThreadLocal> key){

int i = key.threadLocalHashCode & (table.length - 1);

Entry e = table[i];

if (e != null && e.get() == key)

return e;

else

return getEntryAfterMiss(key, i, e);

}

复制代码

获取当前的线程的threadLocals。

如果threadLocals不为null,则通过ThreadLocalMap.getEntry方法找到对应的entry,如果其引用和当前key一致,则直接返回,否则在table剩下的元素中继续匹配。

如果threadLocals为null,则通过setInitialValue方法初始化,并返回。

private Entry getEntryAfterMiss(ThreadLocal> key, int i, Entry e){

Entry[] tab = table;

int len = tab.length;

while (e != null) {

ThreadLocal> k = e.get();

if (k == key)

return e;

if (k == null)

expungeStaleEntry(i);

else

i = nextIndex(i, len);

e = tab[i];

}

return null;

}

复制代码

魔数0x61c88647

生成hash code间隙为这个魔数,可以让生成出来的值或者说ThreadLocal的ID较为均匀地分布在2的幂大小的数组中。

private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode(){

return nextHashCode.getAndAdd(HASH_INCREMENT);

}

复制代码可以看出,它是在上一个被构造出的ThreadLocal的ID/threadLocalHashCode的基础上加上一个魔数0x61c88647的。

这个魔数的选取与斐波那契散列有关,0x61c88647对应的十进制为1640531527。

斐波那契散列的乘数可以用(long) ((1L << 31) * (Math.sqrt(5) - 1))可以得到2654435769,如果把这个值给转为带符号的int,则会得到-1640531527。换句话说

(1L << 32) - (long) ((1L << 31) * (Math.sqrt(5) - 1))得到的结果就是1640531527也就是0x61c88647

通过理论与实践,当我们用0x61c88647作为魔数累加为每个ThreadLocal分配各自的ID也就是threadLocalHashCode再与2的幂取模,得到的结果分布很均匀。

ThreadLocalMap使用的是线性探测法,均匀分布的好处在于很快就能探测到下一个临近的可用slot,从而保证效率。。为了优化效率。

ThreadLocal与内存泄漏

之所以有关于内存泄露的讨论是因为在有线程复用如线程池的场景中,一个线程的寿命很长,大对象长期不被回收影响系统运行效率与安全。如果线程不会复用,用完即销毁了也不会有ThreadLocal引发内存泄露的问题

当我们仔细读过ThreadLocalMap的源码,我们可以推断,如果在使用的ThreadLocal的过程中,显式地进行remove是个很好的编码习惯,这样是不会引起内存泄漏。

如果您必须使用ThreadLocal,请确保在您完成该操作后立即删除该值,并且最好在将线程返回到线程池之前。 最佳做法是使用remove()而不是set(null),因为这将导致WeakReference立即被删除,并与值一起被删除。

c语言中0xde 这怎么用,为什么使用0x61c88647相关推荐

  1. c语言中struct和c++中class实例对比

    前言 实现游戏中简单的打怪升级的功能 c语言中的struct #include <stdio.h>typedef void(*Train)(struct player*, int); ty ...

  2. 关于C语言中printf函数“输出歧视”的问题

    目录 关于C语言中printf函数"输出歧视"的问题 问题描述 探索问题原因 另一种研究方法 问题结论 关于C语言中printf函数"输出歧视"的问题 问题描述 ...

  3. C语言中“野指针”、“悬空指针”是什么?

    目录 1."野指针"(wild pointer) 2."悬空指针"(dangling pointer) 1."野指针"(wild point ...

  4. c+语言+null,C/C++语言中NULL、'\0’和0的区别

    NULL.'\0'和0的值是一样的,都是0,不过它们的表现形式不一样: 1. NULL: 即空指针,不过在C和C++中并不一样.在VS 2013的库文件string.h中可以看到如果定义. 1 /* ...

  5. 关于C语言中的malloc和free函数的用法

    一.malloc()和free()的基本概念以及基本用法: 1.函数原型及说明: void *malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针 ...

  6. python global函数_如何使用python语言中的global关键字获取函数值

    在python语言中,如果在函数外层定义了一个全局变量a,函数内部又有一个同名的局部变量,想要这个全局变量的值改成为局部变量的值,可以使用global.下面利用一个实例说明如何实现这个场景,操作如下: ...

  7. c语言中的if语句_If ... C中的其他语句解释

    c语言中的if语句 Conditional code flow is the ability to change the way a piece of code behaves based on ce ...

  8. C 语言中 void* 详解及应用介绍

    void 在英文中作为名词的解释为 "空虚.空间.空隙",而在 C 语言中,void 被翻译为"无类型",相应的void * 为"无类型指针" ...

  9. java 英文字符 字节_3、在JAVA语言中,每个英文字符占 个字节,每个中文汉字占( )个字节。...

    [判断题]中心原子中的几个原子轨道杂化时,必形成数目相同的杂化轨道. [单选题]集合 用区间表示正确的是 ( ) [单选题]15.Java语言的类间的继承关系是 [单选题]8.编译Java Appli ...

最新文章

  1. 苹果接盘倒下的无人车公司:吴恩达旗下,曾估值2亿美元,CEO及大部分员工被裁...
  2. mysql自定义函数应用_mysql functions实例:在自定义函数中应用字符串函数
  3. 数据湖之iceberg系列(三)iceberg快速入门
  4. 自定义ArrayList
  5. vue双向数据绑定v-model绑定单选框,复选框,下拉框
  6. LeetCode题——最长无重复子串
  7. java定时任务什么时间e结束_Java定时任务
  8. 移动加权平均全月平均
  9. 搜狗浏览器安装第三方插件
  10. BZOJ4011: [HNOI2015]落忆枫音
  11. Orange Business Services 携手 Riverbed 将 SD-WAN 引入混合网络
  12. C语言入门篇----system命令
  13. 保护你的 Flutter 应用程序
  14. 如何看懂Minecraft报错的关键信息。
  15. 送5本新出版的《剑指offer》
  16. 浅谈www.baidu.com和baidu.com
  17. 关于json对象的使用小结!
  18. Start Additional NameNode
  19. 利用Pyecharts绘制漏斗图的案例【含参数详解】
  20. android状态栏黑色字体,时间电池深色

热门文章

  1. 【已解决】R语言,如何切换镜像?
  2. 给我十分钟带你过完java多线程所有基础知识
  3. 百度html在线编辑器插件,百度编辑器UEditor插件DjangoUeditor v1.8.143
  4. python中用于绘制各种图形、标注文本_python ImageDraw类实现几何图形的绘制与文字的绘制...
  5. Navicat Premium 15连接SQL sever (记录一下我的艰难历程,希望对大家有用,摸了半天终于连接上了)
  6. Linux大作业任务书,《Linux系统管理》期末大作业任务书(计网14级).doc
  7. access 导入 txt sql语句_[内附完整源码和文档] 基于C#和Access的智能聊天机器人
  8. 通信 / 各种协议默认端口汇总
  9. 信仰的力量—海归毕业季的选择与入职后的蜕变记
  10. Black Hat 2021上的七大网络威胁趋势