ThreadLocal 类

ThreadLocal 是什么

ThreadLocal 是一个泛型类,用于在线程中定义局部变量。官方的解释为:这个类提供线程的局部变量。 这些变量与其正常的对应方式不同,因为访问每个线程(通过其 getset 方法)都有自己独立初始化的变量副本

ThreadLocal 的重要说明

ThreadLocalMap 说明

ThreadLocalMapThreadLocal类中的一个内部类(本类和同包的类可以访问)
ThreadLocalMap内部定义了弱引用的数组Entry用来存放keyvalueEntry数组的默认容量为16,并且数组的容量必须为2的次幂

// ThreadLocalMap内部声明的Entry,用于存放key和value,key为ThreadLocal类型的弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
	Object value;
	Entry(ThreadLocal<?> k, Object v) {
    
    
			super(k);
			value = v;
		}
	}
// 内部Entry数组的默认初始容量为16,数组的容量必须为2的幂
// 由于ThreadLocalMap内部有很多(容量-1)的与运算(&)计算数组索引,
// 容量为2的幂可以保证(容量-1)转化为二进制后最后一位始终是1,这样可以有效的减少碰撞几率
private static final int INITIAL_CAPACITY = 16;

// 内部Entry数组
private Entry[] table;

// 数组中存放的元素数量
private int size = 0;

// 数组扩容临界点
private int threshold; 

ThreadLocal存值

  • 通过Thread.currentThread()获取到当前线程对象。(由于Thread里面的ThreadLocalMap变量对象是默认修饰符修饰,而ThreadLocalThread位于相同的包中,所以在ThrealLocal类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过Thread对象可以访问到该线程的ThreadLocalMap对象)
  • 再通过ThreadLocalMapset方法,key为当前ThreadLocal的对象,value为想设置的值,说得直白一点,ThreadLocal存储值,是通过线程自己内部的ThreadLocalMap存储的
  • 而不同的线程自然就有不同的ThreadLocalMap对象变量。如果在同一个线程中声明了两个ThreadLocal对象,而在Thread内部都是使用仅有那个ThreadLocalMap存放数据的

ThreadLocal取值

  • ThreadLocalget方法获取值,就是通过获取到当前线程的对象,然后通过当前线程获取到该线程的ThreadLocalMap对象,最后通过ThreadLocalMap拿到最开始设置的值

ThreadLocalset方法源码

/**
 * ThreadLocalMap 是 ThreadLocal 的一个默认访问修饰符修饰的内部类,也就是说 ThreadLocalMap 可以在 ThreadLocal 内部和同一个包的其他类中使用
 */
public void set(T value) {
    
    
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null)
		// 调用ThreadLocalMap类的set方法
		map.set(this, value);
	else
		// 调用ThreadLocal类的createMap方法
		createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    
    
	// 返回当前线程的内部参数threadLocals,线程对象中threadLocals参数,只要你用不到ThreadLocal,线程对象的这个属性就一直是null
	return t.threadLocals;
}
    
void createMap(Thread t, T firstValue) {
    
    
	// 将当前线程对象作为key,新生成一个ThreadLocalMap对象,并将线程的threadLocals对象设置为这个新生成的ThreadLocalMap
	t.threadLocals = new ThreadLocalMap(this, firstValue);
}
  • 如果getMap不是空,就用ThreadLocalMapset方法置入一个以当前线程对象的键,value为值的这么一个键值对
  • 如果getMap为空,那么就以createMap方法set第一个值

ThreadLocalMap类的set方法

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();
}
  • 计算当前键的hash
  • table里找,重复键就替换值,不重复就在该位置添加这个键值对
  • 当前容量超过阈值就扩容然后rehash()

ThreadLocal类的createMap方法

void createMap(Thread t, T firstValue) {
    
    
	t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap的构造方法

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    
    
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
}

createMap方法的意思就是,构造一个新的ThreadLocalMap对象,将value对象塞进map,然后把传入的线程对象的threadLocals属性指向这个新ThreadLocalMap

ThreadLocalget方法源码

public T get() {
    
    
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
    
    
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
}

private T setInitialValue() {
    
    
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
}

protected T initialValue() {
    
    
	return null;
}

当我们在调用get()方法的时候,先获取当前线程对象,然后获取到当前线程的ThreadLocalMap对象

  • 如果非空,那么取出ThreadLocalvalue
  • 否则进行初始化,初始化就是将initialValue的值setThreadLocal

ThreadLocalsetget 小结

  • ThreadLocal进行set的时候,是在当前线程Thread中获取到有且唯一的ThreadLocalMap对象(如果没有就新建一个ThreadLocalMap对象设置进Thread的属性里),然后把自己作为键,value作为值set进这个Map
  • ThreadLocal进行get的时候,是从当前线程Thread中获取到有且唯一的ThreadLocalMap对象(ThreadThreadLocalMap属性如果为空,也就是说这个线程从来都没有用过ThreadLocal设置过值,返回null),然后把自己做为键去该Map里面找,找到就返回对于的value,没有就返回null

使用 ThreadLocal 的注意点

内存泄漏

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,ThreadLocalMap中就会出现keynullEntry,就没有办法访问这些keynullEntryvalue,如果当前线程再迟迟不结束的话,这些keynullEntryvalue就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏

ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocalget(),set(),remove()的时候都会清除线程ThreadLocalMap里所有keynullvalue

为什么用弱引用

key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。ThreadLocalMap在调用set、get、remove的时候会清除Entry数组中所有key为空的Entry,这样就避免了因为value没被回收而造成内存溢出。

由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除

参考:https://blog.csdn.net/lyandyhk/article/details/50953997

猜你喜欢

转载自blog.csdn.net/weixin_38192427/article/details/113277829