ThreadLocal与AOP方式实现读写分离以及ThreadLocal的注意点

整体实现思路:

  1. 拦截所有service层中所有方法,并通过方法名,去标记使用哪一个库

  2. 通过继承AbstractRoutingDataSource重写determineCurrentLookupKey,来确定每一个线程要使用的数据源。(由于不同的线程,需要不同的数据源,所以使用ThreadLocal来标记库名,保证库名的线程安全问题)

GitHub源码(线上源码,package:cn.edu.cdcas.partyschool.aspect)


如何安全的使用ThreadLocal,避免内存泄漏的问题?

什么是内存泄漏?

  在 Java 中内存泄漏是指,GC 不能释放掉已经没有在使用的某一对象的内存空间。

ThreadLocal 部分源码摘抄

//ThreadLocal对象的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);
    }

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

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

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 数据结构,在这个结构中使用 Entry 来存储 ThreadLocal< Thread >的弱引用 --> ThreadLocal值的键值对。

为什么 ThreadLocal 会出现内存泄漏?

  从上面的源码可知,Entry 对象中的 key 为 ThreadLocal< Thread > 的弱引用类型,在 GC 执行时,会回收掉 key 这个实例对象,那么 key=null 了,但该 Entry 对象并不会被回收,由此便出现了一个 key 为 null 的不能使用到的 value 值。所以,这 key=null 的 Entry 对象便形成了内存泄漏。

如何避免内存泄漏?

  1. 最好的办法就是使用 ThreadLocal 的 remove() 方法来删除使用完后的数据
  2. 在 ThreadLocal 的 set() 方法中,也包含了 replaceStaleEntry() 、rehash() 方法将所有键为 null 的 Entry 的值设置为 null ,也可以避免内存泄漏。
 private void set(ThreadLocal<?> key, Object value) {
            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.
            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();
}

你可能感兴趣的:(Java-SE)