ThreadLocal源码分析与使用场景

来源:互联网 发布:网络什么最赚钱的方法 编辑:程序博客网 时间:2024/05/19 01:08

一、概述

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。
ThreadLocal实现的思路:Thread类中持有一个ThreadLocalMap的引用,用于存储每一个线程的变量副本,这个map在使用ThreadLocal变量时候被延迟创建和初始化,并在线程退出时候被释放。Map中元素的键为this所指向的ThreadLocal实例(并不是所以为的线程对象),而值对应需要使用的变量的副本。下面上源码

二、源码分析

1. 关于ThreadLocalMap属于Thread还是ThreadLocal之争

publicclass Thread implements Runnable {    /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;   }

以上可以看到Thread类中持有的ThreadLocalMap ();同时,注释中指出了这个变量由ThreadLocal类来维护,下面就是今天的主角ThreadLocal;所以,这个map是被线程所持有的,但是其初始化和维护都是在ThreadLocal中。

2. ThreadLocal中的四个方法

2.1 get方法 及其调用的方法

public T get() {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null) {        ThreadLocalMap.Entry e = map.getEntry(this);        if (e != null)            return (T)e.value;    }    return setInitialValue();}/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param  t the current thread * @return the map */ThreadLocalMap getMap(Thread t) {    return t.threadLocals;}/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */private T setInitialValue() {    T value = initialValue();    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null)        map.set(this, value);    else        createMap(t, value);    return value;}/** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map * @param map the map to store. */void createMap(Thread t, T firstValue) {    t.threadLocals = new ThreadLocalMap(this, firstValue);}

可以看到,threadLocals最初是从线程t获取。若尚未初始化,则调用 setInitialValue()

2.2 set方法

/** * 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);    if (map != null)        map.set(this, value);    else        createMap(t, value);}

这里就可以看到,map的key并非原以为的当前线程对象,而是this,这时候this的指向应该是当前的ThreadLocal对象

2.3 remove方法

/** * Removes the current thread's value for this thread-local * variable.  If this thread-local variable is subsequently * {@linkplain #get read} by the current thread, its value will be * reinitialized by invoking its {@link #initialValue} method, * unless its value is {@linkplain #set set} by the current thread * in the interim.  This may result in multiple invocations of the * <tt>initialValue</tt> method in the current thread. * * @since 1.5 */ public void remove() {     ThreadLocalMap m = getMap(Thread.currentThread());     if (m != null)         m.remove(this); }

无F可说

2.3 ThreadLocalMap–一个静态内部类

/** * ThreadLocalMap is a customized hash map suitable only for * maintaining thread local values. No operations are exported * outside of the ThreadLocal class. The class is package private to * allow declaration of fields in class Thread.  To help deal with * very large and long-lived usages, the hash table entries use * WeakReferences for keys. However, since reference queues are not * used, stale entries are guaranteed to be removed only when * the table starts running out of space. */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;        }    }    /**     * The initial capacity -- MUST be a power of two.     */    private static final int INITIAL_CAPACITY = 16;    /**     * The table, resized as necessary.     * table.length MUST always be a power of two.     */    private Entry[] table;...

可以看到,ThreadLocalMap是ThreadLocal的一个静态内部类,其中又有一个继承了WeakReference的Entity,是一个定制化的hashMap.

三、show the code

public class LeThreadLocal extends Thread {    private static ThreadLocalValue threadLocalValue;    ThreadLocal<ThreadLocalValue> threadLocal = new ThreadLocal<>();    public static void main(String[] args) {        for (int i = 0; i < 5; i++) {            LeThreadLocal t = new LeThreadLocal();            t.start();        }    }    @Override    public void run() {        for (int j = 0; j < 5; j++) {            getThreadLocalValue().setIndex(getThreadLocalValue().getIndex() + 1);            try {                Thread.sleep(10);            } catch (InterruptedException e) {                e.printStackTrace();            }            getThreadLocalValue().setS(Thread.currentThread().getName());            System.out.println(getThreadLocalValue());        }    }    public ThreadLocalValue getThreadLocalValue() {        if (threadLocal.get() == null) {            threadLocal.set(new ThreadLocalValue(null, 0));            getThreadLocalValue();        }        return threadLocal.get();    }}class ThreadLocalValue {    private String s;    private int index;    public String getS() {        return s;    }    public void setS(String s) {        this.s = s;    }    public int getIndex() {        return index;    }    public void setIndex(int index) {        this.index = index;    }    public ThreadLocalValue(String s, int index) {        this.s = s;        this.index = index;    }    @Override    public String toString() {        return "ThreadLocalValue{" +                "s='" + s + '\'' +                ", index=" + index +                '}';    }}

测试输出

ThreadLocalValue{s='Thread-4', index=1}ThreadLocalValue{s='Thread-0', index=1}ThreadLocalValue{s='Thread-1', index=1}ThreadLocalValue{s='Thread-2', index=1}ThreadLocalValue{s='Thread-3', index=1}ThreadLocalValue{s='Thread-4', index=2}ThreadLocalValue{s='Thread-0', index=2}ThreadLocalValue{s='Thread-3', index=2}ThreadLocalValue{s='Thread-2', index=2}ThreadLocalValue{s='Thread-1', index=2}ThreadLocalValue{s='Thread-1', index=3}ThreadLocalValue{s='Thread-4', index=3}ThreadLocalValue{s='Thread-0', index=3}ThreadLocalValue{s='Thread-3', index=3}ThreadLocalValue{s='Thread-2', index=3}ThreadLocalValue{s='Thread-1', index=4}ThreadLocalValue{s='Thread-3', index=4}ThreadLocalValue{s='Thread-4', index=4}ThreadLocalValue{s='Thread-0', index=4}ThreadLocalValue{s='Thread-2', index=4}ThreadLocalValue{s='Thread-1', index=5}ThreadLocalValue{s='Thread-0', index=5}ThreadLocalValue{s='Thread-3', index=5}ThreadLocalValue{s='Thread-4', index=5}ThreadLocalValue{s='Thread-2', index=5}

分析: ThreadLocal变量的使用不同于普通变量,对于本例中的静态成员变量而言,应该在每次使用该变量的时候都首先在ThreadLocal中获取这个变量,而不是直接使用其引用进行操作。这也充分印证了,ThreadLocal为每个线程维护了一个副本,而不是多线程操作这个共享的变量。也就是说使用ThreadLocal类去维护一个变量,并不是为了让多线程共同去的效果叠加的操作这个变量,而是互不干涉的使用这个变量的副本,他们的效果不能叠加。

例子中的使用方式是,在可执行(Runnable)的类中维护一个ThreadLocal实例,并在初次使用需要隔离的变量时候,调用threadLocal.set(value)方法,并在后续操作和使用隔离变量value的时候都先在threadLocal中get,而不是直接操作变量的引用。
在Session维护的场景中也经常用到ThreadLocal,其使用方式是将比较宝贵的连接或者会话资源保存在threadLocal中,在这个线程的声明周期内都使用这个session;

易错点:
即便是ThreadLocal中变量,在使用时要先在threadLocal中get,而不是直接操作器引用,尤其是在类静态变量的使用上;要时刻想着去拿副本
在ThreadLocalMap中,key是当前threadLocal的实例,所以一个ThreadLocal只是维护了一个变量,如果有两个变量,那么需要用两个ThreadLocal的实例。

四、关于内存泄漏

这里写图片描述
(参考来源: http://blog.csdn.net/wudiyong22/article/details/52141608)

ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需要存储的Object。也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是图中的虚线,表示ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

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,都调用它的remove()方法,清除数据

原创粉丝点击