ThreadLocal

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

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

可以看到先是拿到当前线程,然后拿到当前线程的threadLocals,这个就是ThreadLocalMap。然后再以当前threadlocal对象实例为键拿到这个map里的一个entry。

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.refersTo(key))
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

先看下ThreadLocalMap的结构

计算位置时,是以当前threadlocal的threadLocalHashCode与上这个table的长度减1,(一个线程可以创建多个ThreadLocal),注意这个threadLocalHashCode刚开始是0,然后每创建一个threadlocal,就加1640531527 (第一个threadlocal是1640531527 )。不过这个hashcode并不是外面new ThreadLocal<>()对象的哈希值,只是用来在ThreadLocalMap里做哈希映射的。

// hash code
private final int threadLocalHashCode = nextHashCode();
 
// AtomicInteger类型,从0开始
private static AtomicInteger nextHashCode =
    new AtomicInteger();
 
// hash code每次增加1640531527 
private static final int HASH_INCREMENT = 0x61c88647;
 
// 下一个hash code
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

然后getEntry()方法如果找到key就直接返回,如果没找到就调用getEntryAfterMiss()方法往后找,因为ThreadLocalMap解决哈希冲突就是先根据哈希值计算位置,如果这个位置被占用了,就从这个位置开始往后遍历找空位置。

getEntryAfterMiss()方法如果找到返回,遇到key为null时,就会调用expungeStaleEntry()方法,最后还没找到返回null。这里要说的是entry的key是弱引用,如果一个ThreadLocal没有外部强引用来引用它,下一次系统GC时,这个ThreadLocal必然会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value。就可能会造成内存泄漏,这个expungeStaleEntry()方法就会将这些value=null,然后下次gc时value所引用的对象将会被回收,这个跟ArrayList的clear方法很像。但其实也没办法直接清除对象,只能这样先让这个对象没有引用,然后等gc。

set、get、remove方法,在遍历的时候如果遇到key为null的情况,都会调用expungeStaleEntry方法来清除key为null的Entry。

但是如果当前线程一直在运行,并且一直不执行get、set、remove方法,这些key为null的Entry的value就会一直存在一条强引用练:Thread -> ThreadLocalMap -> Entry -> value,导致这些key为null的Entry的value永远无法回收,造成内存泄漏。

为了避免这种情况,我们可以在使用完ThreadLocal后,手动调用remove方法,以避免出现内存泄漏。

posted @ 2022-02-10 12:07  YUKINO62  阅读(39)  评论(0编辑  收藏  举报