为什么网站浏览不是做的那样,wordpress exif,贵阳网站建设制作价格,给公司做网页收多少钱个人博客
详解Java ThreadLocal | iwts’s blog
Java ThreadLocal
ThreadLocal提供了线程内存储变量的能力#xff0c;这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。
TreadLocal存储模型
ThreadLocal的静态…个人博客
详解Java ThreadLocal | iwts’s blog
Java ThreadLocal
ThreadLocal提供了线程内存储变量的能力这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。
TreadLocal存储模型
ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组tableThreadLocal确定了一个数组下标而这个下标就是value存储的对应位置。 这样看太抽象了具体数据结构可以从存储方法开始看。
根据set方法探究ThreadLocal存储模型 看上去简单粗暴先是获取线程然后看能不能获取数据结构能获取就直接set获取不到就初始化。
那么这里指向了一个核心方法ThreadLocalMap是怎么获得的。 这个东西竟然是存储在线程对象里面的 等于说TreadLocal的核心内部类是存在一个线程中的。 这个时候再回过头来看ThreadLocalMap 这个Entry继承了WeakReference代表他是一个弱引用而其泛型则是ThreadLocal那么说明了单个Entry节点底层是一个kv结构key是ThreadLocal的弱引用value是一个Object。
但是这个只是Entry节点ThreadLocalMap的底层并不是KV结构而是一个Entry数组。
这里再继续看set()方法是怎么处理的 可以看出来Entry的key就是这个具体的ThreadLocal对象value是具体设置的value。
那么这个下标怎么计算的 这里与计算比较类似HashMap保证数组长度是2的幂这样与运算直接就是取模了具体内容参考下面。
综上可以得到ThreadLocal究竟是怎么存的 注意key存储的是ThreadLocal的弱引用那么扩展到JVM内存模型下 ThreadLocal本身的引用是正常放在栈中的但是由于WeakReference所以key对ThreadLocal的引用是虚引用。这也是OOM的主要原因。
Thread中threadLocals
整个ThreadLocal的数据都是维护在单个线程的属性中所以保证了线程间的隔离因为这玩意就是线程自己的一个变量。
ThreadLocalMap
ThreadLocal的内部类其中定义了Entry节点存储具体的数据。而本身提供了Entry数组用于存储线程下全部的ThreadLocal数据。
此外提供了一系列数组操作方法以及扩容等操作。
Entry
内部类Entry继承了WeakReference其内部设置属性value实现了一个类似KV结构的数据结构保证了key是ThreadLocal的一个弱引用value是具体的数值。
ThreadLocalMap的维护
getMap()
等于从线程中获取线程本身的ThreadLocalMap对象。
ThreadLocalMap的初始化
java.lang.ThreadLocal#createMap
直接走了构造方法 初始化了Entry数组。初始化数组长度为16并且设置了扩容阈值threshold。具体看下面。
扩容机制
类似HashMap扩容初始化长度为16后续扩容的时候直接*2保证长度是2的幂。只有在超过阈值的时候才会扩容。
阈值threshold的维护为当前数组长度的2/3。
跟HashMap一样扩容后需要对旧值重新hash定位到确定的下标。相比HashMap简单多了。
ThreadLocal对象hash定位下标 算法看起来还是比较简单的。了解HashMap这里没啥难度由于长度必然是2的幂这样与计算也是和取模的效果是一样的。
主要是这个threadLocalHashCode是什么。
这里算是一个小优化了主要获取方法 这里走CAS执行一次自增增量为常量0x61c88647。
这样0x61c88647是斐波那契散列乘数那么后续的自增导致hash出来的结果分布会比较均匀可以很大程度上避免hash冲突下面是15次操作获取的hash值 hash冲突
由于ThreadLocalMap的底层是一个数组设计上也不想太复杂所以没有采用HashMap的拉链法而是采用线性探测来解决hash冲突。 非常简单其实就是1看是否越界越界设置0。其实这里改成取模更加简洁。
ThreadLocal弱引用与内存泄漏
回到最上面Entry的设计继承了WeakReference表明了key的对象是个弱引用。
那么在ThreadLocal执行完毕后如果没有及时清除就会导致ThreadLocal对象没有具体的引用对象。那么就会被GC回收掉。
参考上面的图GC回收的是堆堆中的这个key他引用的是ThreadLocal也就是说这个key的引用是会被GC回收掉的。
而Entry中value是个普通的Object那么value本身是不会被回收的。 这样在大量操作后就会导致value无法回收导致内存泄漏。
解决方案
ThreadLocal使用结束后及时remove()。
而JDK为了解决这个问题本身也或多或少在帮助我们回收。
remove()的时候会主动触发而get()、set()在执行的时候也会间接执行expungeStaleEntry()方法。
这个方法会主动清除所有Entry中key为null的对象。
强引用解决方案
比较奇葩感觉用的不多。
即利用static修饰ThreadLocal这样就能保证ThreadLocal对象是强引用从根本上解决问题。
为什么选择用弱引用
依然是OOM问题。
由于ThreadLocalMap的生命周期跟Thread一样长如果是强引用那么手动删除对应key的情况下仍然会导致内存泄漏。
但是使用弱引用可以多一层保障弱引用ThreadLocal被清理后key为null对应的value在下一次ThreadLocalMap调用set、get、remove的时候可能会被清除。
弱引用虽然会导致OOM但是强引用不仅会导致OOM还会更多。
ThreadLocal API
都比较简单感觉没什么好聊的。
需要注意的是hash冲突问题由于hash冲突的解决方案是线性探测所以在get的时候需要对比key是否一致如果不一致可能是hash冲突需要利用线性探测循环遍历一轮数组。