ThreadLocal源码分析与使用场景

一、概述

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

二、源码分析

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

public
class 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()方法,清除数据

ThreadLocal 是 Java 中的一个类,它可以让我们在多线程环境下实现线程安全的数据共享,其作用是为每个线程提供独立的变量副本,每个线程都可以访问自己所拥有的变量副本,从而避免了线程安全问题。 以下是一些 ThreadLocal 使用场景: 1. 线程池中的线程对象共享数据:线程池中的线程对象是可以被多个任务共享的,如果线程对象中需要保存任务相关的数据,使用 ThreadLocal 可以保证线程安全。 2. Web 应用中的请求处理:在 Web 应用中,一个请求通常会被多个线程处理,每个线程需要访问自己的数据,使用 ThreadLocal 可以确保数据在每个线程中的独立性。 3. 数据库连接管理:在数据库连接池中,不同的线程需要访问自己的数据库连接,使用 ThreadLocal 可以保证每个线程访问自己的数据库连接,避免线程安全问题。 4. 日期格式化:Java 中的 SimpleDateFormat 类不是线程安全的,如果多个线程同时访问同一个 SimpleDateFormat 对象,会导致线程安全问题,可以使用 ThreadLocal 将 SimpleDateFormat 对象放在 ThreadLocal 中,每个线程都可以访问自己的 SimpleDateFormat 对象。 5. 用户登录信息的保存:在 Web 应用中,用户的登录信息需要在整个应用中共享,但每个线程都需要访问自己的用户信息,使用 ThreadLocal 可以保证线程安全。 总之,使用 ThreadLocal 可以很方便地解决多线程环境下的数据共享问题,但需要注意的是,如果不合理使用 ThreadLocal,会导致内存泄漏问题,需要及时清理 ThreadLocal 中的数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值