ThreadLocal工作原理

ThreadLocal原理

1.threadLocal官方解释是线程本地变量意思。从实际应用来看,Threadlocal主要存储关键资源等(例如:Session,Connection)。ThreadLocal内部通过ThreadLocalMap进行存储,其中key是threadlocal,value是存储的资源对象。ThreadLocalMap是ThreadLocal的静态内部类,ThreadLocal内部还有一个静态内部类Entry主要是构造数据。
2.ThreadLocal中最重要的3个方法:
    2.1一个是initialValue()这个是受保护的方法,使用时需要重新覆盖。
    2.2 get():通过ThreadLocal的get()方法获取当前线程的对应的value对象。
    2.3set(T value):将当前value和当前线程对象存储到ThreadLocal的ThreadLocalMap中。
    2.4remove():删除当前线程对象绑定的对象资源
  • initialValue方法

ThreadLocal中initialValue方法返回值为null,开发人员在必要时需要进行重写。可以通过匿名内部类方式进行重写。

  protected T initialValue() {
        return null;
    }
  • set(T value)方法

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
1. 使用ThreadLocal存储进行存储对象时,set()首先根据当前线程对象去获取ThreadLocalMap对象实例。如map对象实例存在,则将当前值value对象存入到ThreadLocalMap中。如果是第一次存入,则需要创建这个ThreadLocalMap对象。通过createMap方法。下面逐个解释:
 1.1Thread t = Thread.currentThread():获取当前线程的对象
 1.2 ThreadLocalMap map = getMap(t);根据当前线程对象实例获取ThreadLcoalMap对象,这个map是真正存储资源对象的。
 /**
     * 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;
    }
1.2.1通过getMap方法可以看出,该方法只是返回Thread的一个类型为ThreadLocalMap的变量。在Thread类中有一个类型为ThreadLocal.ThreadLocalMap的变量threadLocals。
1.3如果ThreadLocalMap不为空,则通过map.set(key,value)方法。即 map.set(this, value)进行存储当前线程绑定的资源对象。其中key值为当前threadLocal对象实例,这是因为一个线程中可能会有多个ThreadLocal变量。下面我们可以看看ThreadLocalMap这个内部类:
 static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
 1.3.1 ThreadLocalMap这个内部类中还有一个内部类Entry。这个内部类的构造函数中参数为ThreadLocal,和值为Object类型的。最终ThreadLocalMap中的key,value是以Entry对象实例形式存储。因为第一次存储时实例化ThreadLocalMap对象时以这种方式存储(下文讲解)。
1.4 如果getMap(t)得到的ThreadLocalMap为空,则执行createMap(t, value);这条语句。可以看看源码:
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
 /*** 
    *  Construct a new map initially containing                    *(firstKey, firstValue).
    *ThreadLocalMaps are constructed lazily, so we only create
 - one when we have at least one entry to put in it.
  */
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
1.4.1createMap()方法中,第一次实例化ThreadLocalMap时指定当前ThreadLocal实例为key,value为需要存储的对象。其中key,value作为类Entry构造参数,同时存储到Entry[]类型数组中。
  • get()方法

    1.通过get方法获取当前线程对象绑定的资源对象。如果是第一次获取,则需要重写initialValue()方法。源码如下:

 /**
 * 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)
                return (T)e.value;
        }
        return setInitialValue();
    }

源码中获取当前线程绑定的资源对象变量,就是通过getMap(t)获取当前线程对象的ThreadLocalMap实例,然后从map中获取根据key-ThreadLocal获取所需要内容。
如果当前map对象不存在,则通过setInitialValue()方法获取。这种情况属于第一次将资源对象与ThreadLocal进行绑定。

 /**
    * 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;
    }

上述源码中,initialValue()方法默认返回null,需要开发人员覆盖该方法。如果是第一次绑定执行到createMap方法中。

  • remove()方法

    remove方法主要是将当前线程对象与绑定的资源对象的key-value关系解除,并不是真正销毁对象。可以看看源码:

  public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

ThreadLocal实际应用
ThreadLocal不是解决资源同步问题的,主要是解决多线程环境下线程非安全资源的使用。通过ThreadLocal将每个线程都绑定一个共享资源的”副本”,我觉得副本这个词不是很准确。比如在Spring中,数据库连接connection的管理就是通过ThreadLocal解决的。在Spring中的DataUitls类中有如下:

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        Assert.notNull(dataSource, "No DataSource specified");

        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
            conHolder.requested();
            if (!conHolder.hasConnection()) {
                logger.debug("Fetching resumed JDBC Connection from DataSource");
                conHolder.setConnection(dataSource.getConnection());
            }
            return conHolder.getConnection();
        }
        // Else we either got no holder or an empty thread-bound holder here.

        logger.debug("Fetching JDBC Connection from DataSource");
        Connection con = dataSource.getConnection();

        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            logger.debug("Registering transaction synchronization for JDBC Connection");
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
            ConnectionHolder holderToUse = conHolder;
            if (holderToUse == null) {
                holderToUse = new ConnectionHolder(con);
            }
            else {
                holderToUse.setConnection(con);
            }
            holderToUse.requested();
            TransactionSynchronizationManager.registerSynchronization(
                    new ConnectionSynchronization(holderToUse, dataSource));
            holderToUse.setSynchronizedWithTransaction(true);
            if (holderToUse != conHolder) {
                TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
            }
        }

        return con;
    }

1.这是Spring中DataUtils类中获取connection的方法,其中Connection通过ConnectionHolder管理,而ConnectionHolder是通过TransactionSynchronizationManager获得的。就是上文中的第一句代码:

ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);

2.TransactionSynchronizationManager这个类中封装了很多ThreadLocal变量。
3.上述代码中,如果获得ConnectionHolder的对象为空,则通过dataSource.getConnection()方法获得数据库连接,然后构造ConnectionHolder对象实例holderToUse = new ConnectionHolder(con);,将holderToUse和dataSource作为适配器ConnectionSynchronization的参数注册到TransactionSynchronizationManager中的synchronizations中。
最后将holderToUse与dataSource作为Map对象存储到ThreadLocal中。具体源码如下:

public static void bindResource(Object key, Object value) throws IllegalStateException {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        Assert.notNull(value, "Value must not be null");
        Map map = resources.get();
        // set ThreadLocal Map if none found
        if (map == null) {
            map = new HashMap();
            resources.set(map);
        }
        Object oldValue = map.put(actualKey, value);
        // Transparently suppress a ResourceHolder that was marked as void...
        if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
            oldValue = null;
        }
        if (oldValue != null) {
            throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
                    actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
                    Thread.currentThread().getName() + "]");
        }
    }

ThreadLocal总结
学习ThreadLocal主要是理解掌握ThreadLocal的思想和设计方法,并灵活运用。

你可能感兴趣的:(J2SE,线程,ThreadLoca)