深入分析ThreadLocal

来源:互联网 发布:linux系统是什么 编辑:程序博客网 时间:2024/06/05 19:13
 在JDK1.2 版本中,提供了java.lang.ThreadLocal。它为解决多线程并发问题提供了一种新的思路,有其特定的应用场景。

1. 如何理解ThreadLocal
ThreadLocal,很多人叫它线程本地变量。它为每个线程都创建一个副本,每个线程访问自己内部的副本变量。不会影响其他线程的副本变量。变量是同一个,但是每个线程都使用此变量的一个新的副本,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
ThreadLocal一般可用于数据库连接的管理,可看下边的例子:

public class ConnectionManage private static Connection connection = null;     /**     * 获取数据量连接     */    public static Connection openConnection() {        if (connection == null) {            connection = DriverManager.getConnection();        }        return connection;    }    /**     * 关闭数据库连接     */    public static void closeConnection(){        if (connection!=null){            try {                connection.close();            } catch (SQLException e) {                e.printStackTrace();            }        }    }}
上边的代码在多线程中使用会出现问题。因为connection是共享变量,这两个方法没有进行同步,可能在openConnection方法中,多次创建connection。另外,可能一个线程在执行数据库操作时,另个一个线程就调用了closeConnection方法,将数据库连接关闭了。所以,如果要保证上边的代码正确运行,必须进行同步。但是,这会大大影响执行效率,因为,一个线程在使用Connection时,其他线程就必须等待。我们可以这样思考,是不是可以不将connection变量设为线程私有变量,不进行共享。这样是可以的,如果直接声明成:
private Connection connection = null;

然后,给每个线程创建私有的变量。这样是可以实现线程安全的,但是,这创建了很多个数据库连接,这会使服务器压力很大,严重影响程序的执行效率。并且,数据库连接一般是设计为单例的。所以,不能设计成这种形式。
这种情况下,就可以使用ThreadLocal。ThreadLocal在每个线程都为共享变量创建了副本,即:会在每个线程的内部都会有一个该变量,且不同线程之间不会影响,从而达到了线程隔离的效果。

1.ThreadLocal的实现
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份共享变量的副本,因此可以同时访问而互不影响。

private static ThreadLocal<Connection> connection = new ThreadLocal<Connection>();

ThreadLocal有4个方法,分别是:

  • public void set(T value):将值放入线程局部变量中
  • public T get():从线程局部变量中获取值
  • public void remove():从线程局部变量中移除值(有助于 JVM 垃圾回收)(jdk1.5)
  • protected T initialValue():返回线程局部变量中的初始值(默认为 null)

    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) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")                T result = (T)e.value;                return result;            }        }        return setInitialValue();    }
从上边的代码可以看到,getMap(t)方法,获得的当前线程的一个ThreadLocalMap。然后获得Entry <key,value>。获取成功,则返回本线程的value。如果,map为空,说明还没有此变量副本。调用setInitialValue()方法。下边,进行分析每一行的具体含义:ThreadLocalMap map = getMap(t);
    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }
从上边的代码可以看到,返回的是当前线程的一个成员变量threadLocals。这个成员变量在Thread类中:定义为:   ThreadLocal.ThreadLocalMap threadLocals = null;这个变量的类型是ThreadLocal类的内部类ThreadLocalMap。然后进入ThreadLocal中,查看此类型实现:
 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;            }        } }
ThreadLocalMap的Entry继承了 WeakReference,并且使用当前ThreadLocal作为键值。这里将ThreadLocal作为键值,是因为可能一个线程中有多个ThreadLocal的变量。而每个线程是 ThreadLocal.ThreadLocalMap threadLocals = null;获得线程独有的Entry的。然后,再看setInitialValue()方法。
   /**     * 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;    }
如果获得的Map不为空,就设置键值对,为空,就调用createMap方法:
    /**     * 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) {        t.threadLocals = new ThreadLocalMap(this, firstValue);    }
设置当前线程的成员变量threadLocals。这基本就是ThreadLocal的实现原理,总结一下就是:在每个线程的Thread内部有一个 ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,它存储了实际的变量副本。键值为当前ThreadLocal变量,value为变量副本。初始时候,在Thread中,threadLocals为空,当ThreadLocal变量调用get()或者set()方法时,会对Thread类中的threadLocals进行初始化,键值为当前ThreadLocal变量,value为变量副本。变量副本是存在线程的Thread类中的threadLocals变量中的。

## 注意点##

  • 在进行get之前,必须进行set,否则会出现空指针。 如果想在set之前就调用get,则必须重写initialValue方法。
    原因是: 如果没有set的话,则map返回为空,则会调用setInitialValue方法,此方法创建副本,并且返回value。value的获得是通过:T value = initialValue();获得的,如果没有重写这个方法,其默认实现是:
    protected T initialValue() {        return null;    }
其返回值是null,从而,没有先set,就调用get时,会返回null,从而报空指针异常。set方法为:
    public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }
  • ThreadLocal常见的应用场景
    一般用ThreadLocal来解决数据库连接和Session管理等。ThreadLocal主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。