java threadlocal

ThreadLocal是什么

ThreadLocal的官方API解释为:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

从上面ThreadLocal官方的文档说明中可以看到,这个与多线程,以及线程间变量共享没有关系,可以这么理解:

  1. ThreadLocal提供了一种访问某个变量的特殊方式:访问到的变量属于当前线程,即保证每个线程的变量不一样,而同一个线程在任何地方拿到的变量都是一致的,这就是所谓的线程封闭
  2. 每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中Local所要表达的意思。
  3. 如果要使用ThreadLocal,通常定义为private static类型,也可以定义为private static final类型。
  4. ThreadLocal的作用是提供线程内的全局变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量传递的复杂度。

ThreadLocal的接口方法

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下。

  1. void set(T value):设置当前线程的线程局部变量的值。
  2. public T get():该方法返回当前线程所对应的线程局部变量。
  3. public void remove():将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法,实际上在JDK 1.4源码里就已经有提供,只是被作者注释了。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以帮助GC工作,避免内存溢出,加快内存回收的速度。
  4. protected T initialValue():返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()set(Object)时才执行,并且仅执行1次。ThreadLocal中的默认实现直接返回一个null

ThreadLocal源码分析

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在Thread类中有一个Map,用于在每一个线程中存储ThreadLocal变量副本,Map中元素的键为ThreadLocal对象本身,而值是ThreadLocal对象set的对象。

下图是ThreadLocal相关对象之间的引用关系图,实线表示强引用,虚线表示弱引用。

首先看一下java.lang.Thread类中部分源码,其中每个线程对象中有个ThreadLocal.ThreadLocalMap对象,这是一个包可见的属性。

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;    // 其他代码省略 ......}

接着看一下ThreadLocal.ThreadLocalMap的相关代码,由类的说明可以知道这是一个类似WeakHashMap的数据结构,实际数据是存储在Entry[]数组中,而Entry是继承WeakReference的,当entry.get()null时,即ThreadLocal对象为null,则说明此引用关联的ThreadLocal对象已经被GC回收,对应的entry也可以从Entry[]数组中移除,并将对应的value置为null,帮助GC回收对象空间,线程运行结束时,线程中ThreadLocalMap变量的所有Entry[]会在下一次GC时被回收。

/** * 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) {            // 此处调用 new WeakReference(threadLocal) 构造方法            // 如果 ThreadLocal 在外部没有强引用时,entry.get() 会返回 null            // 与 WeakHashMap 实现相似            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; // 当前线程的 ThreadLocal 对象及其 value 实际存放的数组    /**     * Set the value associated with key.     *     * @param key the thread local object     * @param value the value to be set     */    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(); // entry 继承 WeakReference,其 get() 方法返回弱引用关联的对象            if (k == key) { // 对象直接用 == 比较其内存地址,判断是否同一个对象                e.value = value;                return;            }            if (k == null) {                // 遍历当前线程的threadLocals时,如发现有key已经被GC回收了,就在清理 table                // 并将 value 重置为 null,帮助GC回收对象,避免内存溢出                replaceStaleEntry(key, value, i);                return;            }        }        tab[i] = new Entry(key, value);        int sz = ++size;        if (!cleanSomeSlots(i, sz) && sz >= threshold)            rehash();    }    // 其他代码省略 ......}

下面看一下ThreadLocal关键的get()set(T)方法。

  1. get()方法比较简单,根据算法得到当前ThreadLocal对象在数组中的索引,再到Entry[]数组获取相应的Entry,并返回其value
  2. set(T)方法中,每个ThreadLocal对象将自身作为ThreadLocalMapkey,和传入的参数value一起被保存到ThreadLocalMap中。
  3. 另外还有个关键方法getMap(Thread t),就是通过这个方法将ThreadLocal与当前线程关联起来的。
/** * 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) {        ThreadLocalMap.Entry e = map.getEntry(this);        if (e != null) {            @SuppressWarnings("unchecked")            T result = (T)e.value;            return result;        }    }    return setInitialValue(); // 如果在 map 中没有取到值,会调用初始化方法}/** * 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); // 从当前线程中取出线程属性 threadLocals    if (map != null)        map.set(this, value); // 最后存放到Entry[]数组中    else        createMap(t, value);}/** * 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 * {@code initialValue} method in the current thread. * * @since 1.5 */ public void remove() {     ThreadLocalMap m = getMap(Thread.currentThread());     if (m != null)         m.remove(this); }/** * 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;}/** * 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 */void createMap(Thread t, T firstValue) {    // this 即为 ThreadLocal 对象,将本身做为此 map 的 key    t.threadLocals = new ThreadLocalMap(this, firstValue);}

TLS介绍

  1. TLS的概念来源于C语言,是线程局部存储(Thread Local Storage)的缩写,也可以说是线程本地存储
  2. TLS只是对全局变量和静态变量才有意义。局部变量存在于具体线程的堆栈上,而每个线程都有自己的堆栈,所以局部量本来就是局部于具体线程的。
  3. 对于有些共享资源,希望所有线程可见,于是在进程级别声明即可(也就是常见的全局变量),这样所有线程都可以任意访问,这时为了防止污染,就需要加锁进行串行访问。
  4. 对于有些资源,仅仅希望在本线程可见,不能让其他线程污染,这样很容易想到在线程内部声明局部变量,但是这么做有个头疼的问题是局部变量有作用域,如果在线程中需要横跨多个函数,那么就需要一层层的参数传递,极麻烦并且易错,为了解决这种问题TLS应运而生。

通过上面的关于TLS的介绍,对比java设计的ThreadLocal,也是一样的目的,了解一下TLS会更易于理解java的ThreadLocal。

与同步机制的比较

  1. 线程同步是因为不同线程之间需要操作共享对象,线程与对象关系为n:1,多个线程操作共享对象是串行操作的。
  2. ThreadLocal中则是不同线程中操作变量名相同的不同对象,线程与对象关系为1:1,一般ThreadLocal是static的,可以理解为线程执行环境中的"全局变量"。
  3. ThreadLocal主要用于线程封闭,并不涉及线程同步
  4. ThreadLocal提供了线程安全的对象封装,可以把非线程安全的变量封装进ThreadLocal,以达到线程封闭
  5. ThreadLocal解决的是同一个线程内的资源共享问题,而线程同步解决的是多个线程间的资源共享问题。

ThreadLocal示例说明

public class ThreadLocalExample {    // 通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {        @Override        public Integer initialValue() {            return 0;        }    };    private static int i = 1;    private static volatile boolean stop = false;    public int getInteger() {        return i++;    }    // 获取下一个序列值    public int getNextNum() {        seqNum.set(seqNum.get() + 1);        return seqNum.get();    }    public static void main(String[] args) {        ThreadLocalExample sn = new ThreadLocalExample();        // 线程共享sn实例        for (int j = 0; j < 100; j++) {            new ThreadLocalExampleThread(sn).start();        }        stop = true;    }    private static class ThreadLocalExampleThread extends Thread {        private final Logger logger = LoggerFactory.getLogger(this.getClass());        private ThreadLocalExample sn;        public ThreadLocalExampleThread(ThreadLocalExample sn) {            this.sn = sn;        }        public void run() {            while (!stop) {                for (int i = 0; i < 100; i++) {                    // 每个线程打出序列值                    logger.info("not thread safe int value is: " + sn.getInteger());                    logger.info("thread[" + Thread.currentThread().getName() + "] --> seqNum[" + sn.getNextNum() + "]");                }            }        }    }}

以上程序运行之后输出的日志最后几行如下:

[ Thread-93] : not thread safe int value is: 9796

[ Thread-10] : thread[Thread-10] --> seqNum[100]

[ Thread-94] : not thread safe int value is: 9797

[ Thread-95] : not thread safe int value is: 9798

[ Thread-96] : not thread safe int value is: 9799

[ Thread-4] : thread[Thread-4] --> seqNum[100]

[ Thread-97] : not thread safe int value is: 9800

[ Thread-22] : thread[Thread-22] --> seqNum[100]

[ Thread-23] : thread[Thread-23] --> seqNum[100]

[ Thread-24] : thread[Thread-24] --> seqNum[100]

从上述结果可以看到100个线程并发执行100次共享对象i的自增操作,在程序中i自增操作实际执行了10000次,但因为JVM将线程本地内存中的值同步回主存时会相互覆盖,所以i最后打出来的值为9800,而并不是10000。但seqNum是一个ThreadLocal类型的变量,虽然100个线程会操作共享对象sn,但是每个线程中的seqNum是相互独立的,线程执行完之后,每个线程最后输出的seqNum都是100。

ThreadLocal常见应用

最常见到的是JDBC中的Connection对象,因为Connection对象并不是线程安全的,一般线程会从连接池中获得一个Connection对象,并且连接池不会再将这个对象分配给其他线程,用此对象来处理完请求之后再还给连接池。

public class ConnectionDispenser {    private static class ThreadLocalConnection extends ThreadLocal {        public Object initialValue() {            return DriverManager.getConnection(ConfigurationSingleton.getDbUrl());        }    }    private static ThreadLocalConnection conn = new ThreadLocalConnection();    public static Connection getConnection() {        return (Connection) conn.get();    }}

也可以理解为ThreadLocal为我们创建了一个线程内的单例对象per-thread-singleton

在EJB调用期间,J2EE容器需要将一个事务上下文Transaction Context与某个执行中的线程关联起来,通过将事务上下文保存在静态的ThreadLocal对象中,当需要判断当前运行的是哪个事务时,只需要从这个ThreadLocal对象中读取事务上下文。这样就避免了在调用每个方法时都要传递执行上下文信息。

因此ThreadLocal变量类似于全局变量,它会降低代码的可重用性,并在类之间引入隐含的耦合性,使用时也需要格外小心。

ThreadLocal的问题

ThreadLocal对象回收问题

  1. ThreadLocal一般不会产生内存泄漏,ThreadLocal对象会随着线程结束一起回收。
  2. 如果线程一直存活并一直堵塞,其内存又不能被回收,可能会导致内存溢出,程序异常。
  3. 手动调用ThreadLocal.remove()方法,及时帮助GC清除数据。
  4. 在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存溢出的问题,更严重的是可能导致业务逻辑出现问题。所以,在连接池中使用ThreadLocal时,就跟加锁之后要解锁一样,用完就清理。

为什么ThreadLocal变量一般都是static的

首先如果是非static变量,那么就是每个线程中每个对象中都有一个ThreadLocal对象实例了,这样并不能实现一个线程里同一个类的不同对象共享一个ThreadLocal对象的要求。

当然也可以声明为非static属性的,有些类库里就是刻意声明为private final的,它提供了per-thread and per-instance的ThreadLocal对象。

当然也可以声明为private static final的,这其实是最常见的用法,而不是private static

为什么实现一个类似WeakHashMap的ThreadLocalMap

这2个类都是JDK 1.2之后引入的,ThreadLocal第一版本实现就是使用了一个synchronizedWeakHashMap的,参考如下源码,因为这个实现中ThreadLocal是并不是绑定线程的,所以需要线程同步,显然性能比较差,后来的JDK 1.2.2后面的版本应该是被Doug Lea重写的,重写之后就不需要进行同步了,完全就是线程内部控制的一个属性对象,另外重写之后就没有使用WeakHashMap

JDK 1.2.2_001中的版本中的ThreadLocal实现代码:

/**  * @author  Josh Bloch  * @version 1.8 07/08/98  * @since  JDK1.2  */public class ThreadLocal {    Map map = Collections.synchronizedMap(new WeakHashMap(53));    public ThreadLocal() {    }    protected Object initialValue() {        return null;    }    public Object get() {        Entry ourEntry = ourEntry(true);        return ourEntry.value;    }    public void set(Object value) {        Entry ourEntry = ourEntry(false);        ourEntry.value = value;    }    private Entry ourEntry(boolean initUponCreate) {        Thread ourThread = Thread.currentThread();        Entry ourEntry = (Entry)(map.get(ourThread));        if (ourEntry == null) {            ourEntry = newEntry(initUponCreate ? initialValue() : null);            map.put(ourThread, ourEntry);        }        return ourEntry;    }    Entry newEntry(Object value) {        return new Entry(value);    }    static class Entry {        Entry(Object value) {this.value = value;}        Object value;    }}

JDK 1.2.2_017中的版本:

/** * @author  Josh Bloch and Doug Lea * @version 1.10 04/18/02 * @since   JDK1.2  */

JDK 1.4.2中的版本:

/** * @author  Josh Bloch and Doug Lea * @version 1.21, 01/23/03 * @since   1.2 */

References

  1. Java基础知识ThreadLocal
  2. ThreadLocal原理分析
  3. JAVA线程本地存储之ThreadLocal的分析
  4. ThreadLocal 内存泄漏问题
  5. 理解 ThreadLocal
  6. 关于TLS
  7. Threading lightly