深入分析ThreadLocal

 在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为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值