ThreadLocal详解

ThreadLocal
Posted in 未分类 - 11 三月 2011 - No comment

昨天听旁边同事在讨论问题,冒出一句:假设我们把这些变量都存储在ThreadLocal中,balabala…
脑子闪了一下,发现自己对变量在ThreadLocal里怎样存储的,并不是很清晰,于是,开始google、翻看ThreadLocal源码。
ThreadLocal是干嘛的

ThreadLocal 为每个线程都提供一个变量的副本,从而保证各个线程间数据安全,每个线程的数据不会被另外线程访问和破坏。

最常用的方法有 set(T value)和 T get()
把变量存储在ThreadLocal里?

事实上,ThreadLocal是一个线程隔离(或者说是线程安全)的变量存储的管理实体,注意,是管理,它并不负责存储。
我们来看ThreadLocal.java中set()和get()的源代码
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
/**
* 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;
}

可以发现,所谓“把变量存储在ThreadLocal中”,实际上是在当前线程(Thread.currentThread())中拿到一个ThreadLocalMap,然后以ThreadLocal为Key,要存储的“变量”为value,put到这个ThreadLocalMap中。ThreadLocalMap就是一个继承自WeakReference的Entry。

在存储过程中,ThreadLocal只是作为承载set和get方法的类,作为存储“变量”的key,而“变量”并没有存储在ThreadLocal中。哪儿去了?在Thread持有的ThreadLocalMap中!

所以,咬文嚼字的话,“我们把这些变量都存储在ThreadLocal中”的说法是不对的,ThreadLocal只是管理实体,“变量”是存储在当前Thread中的。

再来看看 T 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();
}

首先从当前线程中拿到ThreadLocalMap ,然后ThreadLocal为key,取出对象。

ThreadLocal和Thread

Thread和ThreadLocal对变量引用关系可以分为两个维度来看。
首先说Thread。

我们知道一个Thread(暂且命名为ThreadOne)的执行会贯穿多个方法MethodA、MethodB、MethodC,这些方法可能分布于不同的类实例。假设,这些方法分别使用了ThreadLocalA、ThreadLocalB、ThreadLocalC来保存线程本地变量,那么这些变量都存于ThreadOne的Map中,并使用各自的ThreadLocal实例作为key。 因此,可以认为,借助ThreanLocal的set方法,在X轴上,Thread横向关联同一线程上下文中来自多个Method的变量引用副本。

这样,一个线程持有N多属于自己的变量。
接着说ThreadLocal。

一个MethodA中的X变量将被多个线程ThreadOne、ThreadTwo、ThreadThree所访问。假设MethodA使用ThreadLocal存储X,通过set方法,以ThreadLocal作为key值,将不同线程来访时的不同的变量值引用保存于ThreadOne、ThreadTwo、ThreadThree的各自线程上下文中,确保每个线程有自己的一个变量值。因此,可以认为,ThreadLocal是以Method为Y轴,纵向关联了处于同一方法中的不同线程上的变量。

这样,N个线程的变量相互独立,每个线程拿到的都是自己持有的对象,互不相干。
ThreadLocal典型用法

ThreadLocal一个典型用法是,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

例如,在一个web系统中,DAO层需要得到当前User的信息–这很常见,例如我想在日志表中插入loginId–一般User对象都在session中,怎么办?我们要在DAO层,拿着session传来传去?oh-my-cofficecat…现在有了ThreadLocal帮忙,这问题不存在了。来,看代码

User对象:
public class User {

    private String loginId;
    private String passwd;
    private static final ThreadLocal userHolder = new ThreadLocal();

    public static final User getCurrentUser() {
         return userHolder.get();
    }

    public static final void setCurrentUser(User user) {
        userHolder.set(user);
    }

    public static final void removeCurrentUser() {
        userHolder.remove();
    }

    //ignor setter & getter & constructer
}

在请求入口处,把user从session中取出来,放到Thread中
User user = (User) rundata.getSession().getAttribute(sessionKey);
User.setCurrentUser(user);

在DAO层,如果想用User的数据,例如在log表中插入user.loginId
dataObject.setOperatorId(User.getCurrentUser() );

在这个DAO方法中,你完全不用把session或者User作为参数,即可取到user的信息

注意:在整个session结束后,不要忘了User.removeCurrentUser(),否则,你联想一下线程池….恐怖啊
引申:关于WeakReference

看ThreadLocalMap的代码,会发现它本质是一个继承WeakReference的Entity,代码如下
/**
* 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;
       }
}

WeakReference是干啥的?

我们平常用的都是对象的强引用,如果有强引用存在,GC是不会回收对象的。我们能不能同时保持对对象的引用,而又可以让GC需要的时候回收这个对象呢?java中用WeakReference来实现。
弱引用可以让您保持对对象的引用,同时允许GC在必要时释放对象,回收内存。
用途在哪儿?对于那些创建便宜但耗费大量内存的对象,即希望保持该对象,又要在应用程序需要时使用,同时希望GC必要时回收时,即可考虑使用弱引用。

所以,在ThreadLocalMap的javadoc中,有这么一句话

    * To help deal with
    * very large and long-lived usages, the hash table entries use
    * WeakReferences for keys.

本文参考了:
简明扼要,再谈ThreadLocal和synchronized
正确理解ThreadLocal

你可能感兴趣的:(DAO,thread,多线程,Google)