早时总结过《ThreadLocal解析》、《FastThreadLocal解析》
最近看一些资料的时候,又重重发现了这类,不希望再温下,许多知识点,之前已经总结了,这篇文章主要有两个问题:
1、弱引用的意义
2、如何防键冲突
弱引用
写ThreadLocal使用中的ThreadLocalMap一直在进化,早在JDK1.3时代还是使用普通的HashMap,后来才改成ThreadLocalMap
ThreadLocalMap有两个特殊的牵绊,这个是使用了线性法则,详情见《Hashmap底层解析》;二是关键使用了
/** * ThreadLocalMap is a customized hash map suitable only for * maintaining thread local values. No operations are exported * outside of the ThreadLocal class. The class is package private to * allow declaration of fields in class Thread. To help deal with * very large and long-lived usages, the hash table entries use * WeakReferences for keys. However, since reference queues are not * used, stale entries are guaranteed to be removed only when * the table starts running out of space. */ static class ThreadLocalMap{}
从注释作者可以为什么使用弱,以便处理大对象和引用周期对象,在 GC 时可以主动回收
一直觉得这里的弱引用设计是个鸡肋
在实际使用中,还是会出现泄漏,何必弱使用引用呢?
在 ThreadLocal 的类评论中
This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable. {@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID).
建议使用静态物质
阿里规范也有这个建议:
【参考】ThreadLocal 无法共享对象的更新。说明:这个线程是针对一个本地线程内部所有操作共享的,所以设置为三个变量,解决所有线程实例共享此属性,类第一次被以任何时候,只要所有类别的存储空间(这类对象是这个线程内定义的)都可以使用这个属性。
在stackflow上点赞比较多的回答:
因为如果它是一个实例级字段,那么它实际上将是“每线程 - 每实例”,而不仅仅是一个有保证的“每线程”。这通常不是您要寻找的语义。
通常它包含一些对象,例如用户对话、Web 请求等范围内的对象。您不希望它们也属于类实例的子范围。一个 Web 请求 => 一个持久性会话。不是一个 Web 请求 => 每个对象一个持久会话。
结合实际项目中被使用的方案被推荐的,引用都是因为基本也是经常内存的,根据使用静态物质,根本没有被回收的机会,虽然很弱,但一直很强大
所以何必呢,还让这个点成为一个面试的题目,同是程序员,非得难为自己人呢!
上面的问题其实还是从更多人的想法讨论的,就是说ThreadLocal与线程联系上,在线程的背景里的方式ThreadLocal
但从类注释上看,它是个变量:这个类提供线程局部变量,所以还是从变量角度考虑
线程局部是线程内的变量,有类从变量范围内的变量
可以将变量的不同域,不同的域定义为不同的变量。
为了在函数之间共用一个线程,还有一个线程是线程的线程作用域,线程一般是线程中的一个线程。
不管是大的,其实都是死的。而线程,调用变量则它的范围有多大。
Local是跨函数的,不是所有变量但是跨函数的,Thread的函数,而且是动态的。
ThreadLocal是跨职能的,跨哪些职能呢,由线程来定,更灵活
public class ThreadLocalDemo { public static void main(String[] args) { ThreadLocal<Integer> count = new ThreadLocal<Integer>(); //使用count count.set(10); // count不再被使用,可以进行内存回收 System.out.println(""); } }
这个例子中,count不要被回收
我想,此时 JDK 的作者也是左右为难从眼见为实的角度而言,不必就应该进行回收,实现自动回收,这是 Java 最大的特点。
这时候不能回收了,因为它跟10个绑定到了另一边,而且通过计数写入了10个。最后JDK的作者耍了一个小聪明,用弱引用包装了计数,没有干脆利索的进行内存回收,拖拖拉拉的回收,到底,最终实现了变量就用回收的原则,与Java的传统思想一脉相承的原则进行不同
到这里,是知道为什么作者要设计为弱引用了,建议使用线程本地,只是按java为静态,就不是为了防备漏洞,而只是为了达到不使用就恢复的基本原则,在线程范围内的时候只能,曲线救国了
防碰撞
在哈希数据结构中,最重要的一点就是如何更好地设计,避免关键的破坏
普通的hashmap使用的是附件法,把长度设置成2的N次方,还有负载因子
ThreadLocalMap使用的线索探索法,作者使用了哪些奇技淫巧
地图的长度
/** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16;
ThreadLocal 没有设置容量的方法,也不会实例化写入 ThreadLocal 实例下
必须是2的N次方,这个特性在HashMap中一样,就不解释了,详情见《Hashmap源码解析》
负载系数
在 hashmap 中,默认系数为 0.75,在 ThreadLocal 中呢?
/** * Set the resize threshold to maintain at worst a 2/3 load factor. */ private void setThreshold(int len) { threshold = len * 2 / 3; }
负载率因子为2/3
但threadlocalmap还有特别的地方,就是弱引用,key可能变成null,所以在get,set cleankey为null的Entry
tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash();
再看rehash();
/** * Re-pack and/or re-size the table. First scan the entire * table removing stale entries. If this doesn't sufficiently * shrink the size of the table, double the table size. */ private void rehash() { expungeStaleEntries(); // Use lower threshold for doubling to avoid hysteresis if (size >= threshold - threshold / 4) resize(); }
rehash方法确定自己就直接了,先清除=重新元素,再ashsize>threshold-threshold/4
阈值=len * 2 / 3
阈值-阈值/4=len * 2 / 3-(len * 2 / 3)/4= len/2
所以是在清除过渡元素之后,元素数量超过len/2小时,最终正式扩展了所有内容
0x61c88647
hash函数的设计是hash类数据结构的一个重点,空间与时间的平衡是关键
/** * ThreadLocals rely on per-thread linear-probe hash maps attached * to each thread (Thread.threadLocals and * inheritableThreadLocals). The ThreadLocal objects act as keys, * searched via threadLocalHashCode. This is a custom hash code * (useful only within ThreadLocalMaps) that eliminates collisions * in the common case where consecutively constructed ThreadLocals * are used by the same threads, while remaining well-behaved in * less common cases. */ private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
每当创建ThreadLocal时实例这个值都市累加0x61c88647
0x61C88647转化成:2654435769为32位位无整数的的黄金黄金分割值分割值分割值分割值分割值分割值分割值分割值
当容量为2的N次方并且使用了这个散列魔法算法后,元素正好列满了整个容器,也是完美的
这个数字由来,我也大致算算找到了一些资料,这已经不是一个农家的能力范围,只能研究数学老师的:不是用不上,而是你用上的能力
环带
/** * Increment i modulo len. */ private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } /** * Decrement i modulo len. */ private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); }
在寻找下一个位置的时候,threadlocalmap是线性探索的,但逻辑上使用环形结构,虽然这个与防备关系不大,但也是个知识点,一并罗列下突破
参考
一针见血 ThreadLocal
为什么是 0x61c88647?
ThreadLocal源码——黄金分割数的使用