ThreadLocal源码解析与运用(上)

2017-02-16  本文已影响0人  GrooveWind

ThreadLocal需要解决的问题是什么

ThreadLocal如何解决多线程并发问题

以下代码基于JDK1.8版本

  1. 既然每个线程都会拷贝一份变量,那么我们先看一下这个变量会存在哪里。首先来看Thread类:
public class Thread implements Runnable {
    //......

    /* ThreadLocal values pertaining to this thread. This map is maintained 
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    //......
}

显然,变量的存储肯定与ThreadLocalMap有关

2.ThreadLocal类的ThreadLocalMap:

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
首先,ThreadLocalMap是一个静态内部类;其次,通过WeakReference<ThreadLocal<?>>的修饰,ThreadLocal变为一个弱引用指向类;再次,还有一个静态内部类Entry存储键值,其中Key为弱引用的ThreadLocal本身(通过super(k)实例化)。
再看一下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);
}
先初始化一个长度为16的Entry数组,然后通过哈希算法计算出键值的映射关系,再存到数组中就OK了。**顺带一提,这里是一个无锁并发的哈希实现,具体可以参考[世界上最简单的无锁哈希表](http://blog.jobbole.com/39186/)**

对于其他的方法,暂时省略......

ThreadLocal内存泄漏问题

为什么ThreadLocal会产生内存泄漏
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
但是这些被动的预防措施并不能保证不会内存泄漏:
使用线程池的时候,这个线程执行任务结束,ThreadLocal对象被回收了,线程放回线程池中不销毁,这个线程一直不被使用,导致内存泄漏。
分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么这个期间就会发生内存泄漏。
如何避免ThreadLocal内存泄漏
每次使用完ThreadLocal,都要记得手动调用remove()方法,清除数据。

下一篇介绍ThreadLocal的实战应用与扩展

上一篇下一篇

猜你喜欢

热点阅读