关于ThreadLocal的一点记录

Java ThreadLocal

jdk1.2中引入了threadLocal,作为线程私有变量,每个线程持有不同的ThreadLocal的静态内部类ThreadLocalMap的实例引用。

ThreadLocalMap,ThreadLocal的一个静态内部类,用ThreadLocal作为key值,默认容量为16,超过2/3阈值时则扩容为原来的两倍。

ThreadLocal 常用方法

  1. set方法
public void set(T value) {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null)
		map.set(this, value);
	else
		createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
  • 获取当前线程的threadLocalMap,不为空则直接设值,为空则初始化一个ThreadLocalMap。
  1. get方法
public T get() {
        Thread t = Thread.currentThread();
	//1.获取Thread的ThreadLocalMap
        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;
            }
        }
	//3.如果线程的threadLocalMap为空,则设置初始化的值,最终会调用initialValue方法(该方法可以在创建ThreadLocal时重写)
        return setInitialValue();
    }
  1. map.set和map.getEntry方法
private Entry getEntry(ThreadLocal<?> key) {
	int i = key.threadLocalHashCode & (table.length - 1);
	Entry e = table[i];
	if (e != null && e.get() == key)
		return e;
	else
		//循环查找数组中的entry
		return getEntryAfterMiss(key, i, e);
}
private void set(ThreadLocal<?> key, Object value) {
	
	Entry[] tab = table;
	int len = tab.length;
	//1.计算key所在的位置
	int i = key.threadLocalHashCode & (len-1);

	//2.查找key值所在的位置的数据
	for (Entry e = tab[i];
		 e != null;
		 e = tab[i = nextIndex(i, len)]) {
		ThreadLocal<?> k = e.get();
		//3.如果key已经存在,则替换相应的value
		if (k == key) {
			e.value = value;
			return;
		}
		//4.如果key值为空,ThreadLocal的引用已经不存在,则这个过期的entry值替换成对应的key、value
		if (k == null) {
			replaceStaleEntry(key, value, i);
			return;
		}
	}

	tab[i] = new Entry(key, value);
	int sz = ++size;
	//5.清除过期的entry后如果map的容量大于扩容阈值的话,则进行扩容为原来的两倍
	if (!cleanSomeSlots(i, sz) && sz >= threshold)
		rehash();
}

HASH_INCREMENT = 0x61c88647

  • hash冲突问题

    • 当同一个Thread中的新建多个ThreadLocal的实例时。
    • 同一个ThreadLocal设置多次值时。

    后者发生时直接替换掉原来的值,前者发生时则是通过threadLocalHashCode增长指定的值(HASH_INCREMENT)来探测下一个位置是否可用,即线性探测。

  • 为什么是 0x61c88647?

    每创建一个ThreadLocal时threadLocalHashCode都会增长0x61c88647,而0x61c88647的步长能让hash码均匀的分布在2的N次方的数组中。

    0x61c88647是2^31的黄金比例值。

public static void main(String[] args) {
    long l1 = (long) ((1L << 31) * (Math.sqrt(5) - 1));
    System.out.println("as 32 bit unsigned: " + l1);
    int i1 = (int) l1;
    System.out.println("as 32 bit signed:   " + i1);
    System.out.println("MAGIC = " + 0x61c88647);
  }

Why 0x61c88647? https://www.javaspecialists.eu/archive/Issue164.html

ThreadLocal内存泄漏问题

尽管ThreadLocalMap中的Entry是弱引用类型,Thread.exit()时会将线程持有的ThreadLocalMap变量置为null,但还是会存在内存泄漏问题。

当线程长时间存活的场景下(例如使用线程池),所以建议是用ThreadLocal的remove方法,在业务逻辑处理完成时手动清除当前线程的ThreadLocal。

转载于:https://my.oschina.net/u/1187203/blog/1858359

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值