ThreadLocal
作用
- 用于存储线程本地的副本变量,说白了就是为了做到线程隔离。
- 用于确保线程安全。
做个不恰当的比喻,从表面上看ThreadLocal相当于维护了一个map,key就是当前的线程,value就是需要存储的对象。
这里的这个比喻是不恰当的,实际上是ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置
问题
- ThreadLocal是怎么保证了线程隔离的?
- ThreadLocal注释中提到的隐式引用是什么?有什么作用?
- ThreadLocal为什么要用到隐式引用?而不用强引用?
- 据说ThreadLocal会发生内存泄漏?什么情况下会发生内存泄漏?如何避免内存泄漏?
- 使用ThreadLocal有什么需要注意的点?
源码
- 内部重要数据结构
/**
* 用于ThreadLocal内部ThreadLocalMap数据结构的哈希值,用于降低哈希冲突。
*/
private final int threadLocalHashCode = nextHashCode();
/**
* 原子操作生成哈希值,初始值为0.
*/
private static AtomicInteger nextHashCode = new AtomicInteger();
/*
* 用于进行计算出threadLocalHashCode的哈希值。
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* 返回下一个哈希值,让哈希值散列更均匀。
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
还有最重要的一个数据结构:ThreadLocalMap
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
* 必须是 2的次幂。 为了能通过key.threadLocalHashCode & (len-1) 计算出 索引
* 当n为2次幂时,会满足一个公式:(n - 1) & hash = hash % n 且速度比取余快。
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 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中,Entry为什么要继承WeakReference,而不是其他的Reference?简单总结以下几点原因:
- 是为了在Thread线程的执行过程中,key能够被GC掉,从而在需要彻底GC掉ThreadLocalMap时,只需要调用ThreadLocal的remove方法即可。
- 如果是用的强引用,虽然Entry到Thread不可达,但是和Value还有强引用的关系,是可达的,所以无法被GC掉。
虽然Entry使用的是WeakReference虚引用,但JVM只是回收掉了ThreadLocalMap中的key,但是value和key是强引用的(value也会引用null),所以value是无法被回收的,所以如果线程执行时间非常长,value持续不GC,就有内存溢出的风险。所以最好的做法就是调用ThreadLocal的remove方法,把ThreadLocal.ThreadLocalMap给清除掉。
总结
Thread Pool是一把双刃剑,好处略,坏处之一:
如果Thread是从Thread Pool中取出,它可能会被复用,此时就一定要保证这个Thread在上一次结束的时候,其关联的ThreadLocal被清空掉,否则就会串到下一次使用。(手动调用remove)