ThreadLocal数据结构,ThreadLocalMap中的key是弱引用,内存泄漏?场景

参考:

  • https://www.zhihu.com/search?type=content&q=ThreadLocal%E5%8E%9F%E7%90%86
  • https://zhuanlan.zhihu.com/p/167937566
  • https://zhuanlan.zhihu.com/p/139857791

1.数据结构

  • 原本以为是ThreadLocalMap在ThreadLocal中,key:threadId, value: 资源。
  • 但不是的,正好相反。ThreadLocalMap在Thread中,key: ThreadLocal实例对象引用,value:资源。要不然怎么叫ThreadLocalMap呢,里面存储的key是ThreadLocal对象引用。
  • 和HashMap不一样,HashMap解决key冲突是数组+链表;这里的ThreadLocalMap没有链表,解决key冲突:
  • 当发现位置已经有元素时,继续向后寻找,直到找到null位置。

ThreadLocal数据结构,ThreadLocalMap中的key是弱引用,内存泄漏?场景_第1张图片
ThreadLocal数据结构,ThreadLocalMap中的key是弱引用,内存泄漏?场景_第2张图片

2.线程获取属于自己的资源的过程:

  • 资源被ThreadLocal包装。
  • 当线程去获取自己的那一份时,会先根据根据Thread对象获取Thread对象里的ThreadLocalMap,在根据this(即ThreadLocal本身)为key去获取对应的value。
  • 由key(this ThreadLocal实例对象)去获取value过程:
    • ThreadLocal.threadLocalHashCode & (table.length-1) 得到桶的位置,获取Entry e。
  • 即这个value是Thread对象的私有变量副本,随意修改。
public void set(T value) {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null)
	            map.set(this, value); else
	            createMap(t, value);
}
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();
}

getMap方法:

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

3.为什么ThreadLocal是弱引用?

  • 不是ThreadLocal是弱引用,是ThreadLocalMap中的key是弱引用。
  • 防止内存泄漏。
  • 弱引用对象:若这个对象没有强引用,那么gc时,就会将这个对象回收。
  • 如果ThreadLocalMap中的key是强引用,那么如果我们把外面的ThreadLocal设置为null,但ThreadLocalMap中的key(ThreadLocal)仍指向堆内存中的ThreadLocal对象,那么外面ThreadLocal对象 = null, 就白设了,gc时不会回收它,内存泄漏。
  • 如果ThreadLocalMap中的key是强引用,即new WeakReference<>(key)。当外面的设为null后,gc时会回收这个key对象。
static class Entry extends WeakReference<ThreadLocal<?>> {
	/** The value associated with this ThreadLocal. */
	Object value;
	Entry(ThreadLocal<?> k, Object v) {
		super(k);
		value = v;
	}
}

ThreadLocal数据结构,ThreadLocalMap中的key是弱引用,内存泄漏?场景_第3张图片
图片来源:https://zhuanlan.zhihu.com/p/167937566
ThreadLocal数据结构,ThreadLocalMap中的key是弱引用,内存泄漏?场景_第4张图片

图片来源:https://zhuanlan.zhihu.com/p/139857791

4.内存泄漏?怎么预防

  • 上面说到key是弱引用,当外面的强引用设为null, 且gc后,堆内存中的key就会被会后,key = null, 但此时value还在,key都没了,value又不能访问,还没有删除。造成内存泄漏;
  • 每次set或get时,发现key是null,就删除Entry。但如果我再访问这些已经为null的key呢?
  • JDK建议将ThreadLocal变量定义为private static,延长ThreadLocal生命周期,由于一直存在强引用,所以不会被回收。
  • 然后remove它,防止内存泄漏

5.例子,场景?
两种场景:

  • 每个线程需要自己单独实例

  • 实例需要在多个方法中共享,但不希望多线程共享

  • 数据库连接Connection

    • 每个线程里存放连接副本,线程间互不影响。
  • 存储用户Session

private static final ThreadLocal threadSession = new ThreadLocal();

    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }
  • 解决多线程安全问题
  • 比如Java7中的SimpleDateFormat不是线程安全的,可以用ThreadLocal来解决这个问题
public class DateUtil {
    private static ThreadLocal<SimpleDateFormat> format1 = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static String formatDate(Date date) {
        return format1.get().format(date);
    }
}

你可能感兴趣的:(Java基础)