ThreadLocal源码简解

一、ThreadLocal

ThreadLocal,线程副本变量。

ThreadLocal,保证了每个线程都有独立的对象副本,保证了对象的唯一性,可以实现线程安全性。

UML图

如下所示:

set(T value)

调用ThreadLocal的set(T value)和get()方法时,内部会使用到一个ThreadLocalMap。

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    //ThreadLocalMap对象map不存在,则创建map
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

get()

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //获取当前ThreadLocal对象对应的Entry节点
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

二、内部类ThreadLocalMap

ThreadLocalMap是ThreadLocal的一个内部静态类。

/**
 * Construct a new map initially containing (firstKey, firstValue).
 * ThreadLocalMaps are constructed lazily, so we only create
 * one when we have at least one entry to put in it.
 */
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);
}

getEntry()

ThreadLocalMap,是由多个节点(Entry)组成数组Entry[],通过Hash操作确定节点落在数组的哪个下标。

getEntry(ThreadLocal<?> key),可以通过相应的ThreadLocal对象,找到对应的Entry节点。

/**
 * Get the entry associated with key.  This method
 * itself handles only the fast path: a direct hit of existing
 * key. It otherwise relays to getEntryAfterMiss.  This is
 * designed to maximize performance for direct hits, in part
 * by making this method readily inlinable.
 *
 * @param  key the thread local object
 * @return the entry associated with key, or null if no such
 */
private Entry getEntry(ThreadLocal<?> key) {
    //寻找ThreadLocal对象作为key时,对应的下标
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    //当节点的key与对应的ThreadLocal对象相同时,说明是对应的节点。
    if (e != null && e.get() == key)
        return e;
    else
        //找不到则继续向下一个下标遍历。
        return getEntryAfterMiss(key, i, e);
}

三、线程副本变量

Entry类,构造方法是键值对。

每一个Thread对象,都有一个ThreadLocalMap对象的变量,可以存放这些键值对。
键值对的key为当前的ThreadLocal对象,value就是副本变量。
也正是这种结构,使ThreadLocal能够为每一个线程维护变量的副本。

/**
 * 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;
    }
}

四、弱引用

节点Entry类,继承自弱引用(WeakReference)。

强引用(Strong Reference):通常我们通过new来创建一个新对象时返回的引用就是一个强引用,若一个对象通过一系列强引用可到达,它就是强可达的(strongly reachable),那么它就不被回收。
弱引用(Weak Reference):弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
软引用(Soft Reference):软引用和弱引用的区别在于,若一个对象是弱引用可达,无论当前内存是否充足它都会被回收,而软引用可达的对象在内存不充足时才会被回收,因此软引用要比弱引用“强”一些。
虚引用(Phantom Reference):虚引用是Java中最弱的引用,我们通过虚引用甚至无法获取到被引用的对象,虚引用存在的唯一作用就是当它指向的对象被回收后,虚引用本身会被加入到引用队列中,用作记录它指向的对象已被回收。

五、内存泄露

内存泄漏(Memory Leak):是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

如果使用完ThreadLocal,没有用remove()清除掉无用的对象,可能会导致内存泄露。

/**
 * Remove the entry for key.
 */
private void remove(ThreadLocal<?> key) {
    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)]) {
        if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

此处调用了Reference的clear()方法,如下:

/**
 * Clears this reference object.  Invoking this method will not cause this
 * object to be enqueued.
 *
 * <p> This method is invoked only by Java code; when the garbage collector
 * clears references it does so directly, without invoking this method.
 */
public void clear() {
    this.referent = null;
}

结论

ThreadLocal不仅涉及到线程安全,而且还关系到弱引用,内存泄露,非常值得学习。

posted on 2020-01-14 13:50  乐之者v  阅读(288)  评论(0编辑  收藏  举报

导航