ThreadLocal

This class provides thread-local variables.  These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable.  {@code 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).

上文节选自ThreadLocal的 JavaDoc,从形容的内容上看咱们就能够看出 ThreadLocal 的作用是提供线程局部变量,这些局部变量是原变量的正本;ThreadLocal 是为每个线程提供一份变量的正本,由此不会影响到其余线程的变量正本。

源码浅析

咱们先来看看 ThreadLocal下反对的几个罕用操作。set(T value)get()remove();

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);    }}
ThreadLocalMap getMap(Thread t) {    return t.threadLocals;}
 void createMap(Thread t, T firstValue) {    t.threadLocals = new ThreadLocalMap(this, firstValue);}

从源代码能够看出,set 操作先获取以后线程,再获取以后线程的 ThreadLocalMap ,如果 map 不为空则把以后 ThreadLocal 对象实例作为key,传进来的value作为值,否则创立一个map,再依照键值对放进去。从这里能够看出,本质上咱们最初的存储介质就是这个ThreadLocalMap ,那这个ThreadLocalMap是什么呢?接着往下看。

public class Thread implements Runnable {    ...    ThreadLocal.ThreadLocalMap threadLocals = null;    ... }==================================================================static class ThreadLocalMap {        static class Entry extends WeakReference<ThreadLocal<?>> {    /** The value associated with this ThreadLocal. */    Object value;    Entry(ThreadLocal<?> k, Object v) {        super(k);        value = v;        }    }...    }

每个Thread对象都保护一个ThreadLocalMap 类型的变量 threadLocals ;这个ThreadLocalMapThreadLocal 的一个外部类, ThreadLocalMap 外部保护了一个Entry (键值对),这里能够看到 Entry 继承了 WeakReference ,其对应的构造方法里 ,key值调用了父类的办法,那么意味着Entry 所对应的key(ThreadLocal对象实例)的援用是一个弱援用。

WeakReference 是java四种援用用中的弱援用,当有gc产生时就会被回收。
还有其余三种别离是强援用,虚援用,软援用。

那为什么这里设置成弱援用呢?次要是为了避免内存透露,上面咱们来剖析一下。


假如咱们在办法外部new了一个ThreadLocal对象,并往里面set 值。此时堆内存中的 ThreadLocal 对象有两块援用指向它,第一个援用是栈内存的强援用;另外一个是当set 值之后Entry的key作为的弱援用。办法完结时,当指向 ThreadLocal 对象的强援用隐没时,对应的弱援用也会主动被回收。
咱们假如Entry 中的援用是强援用,当指向ThreadLocal 对象的强援用隐没时,ThreadLocal对象应该被回收但却因为Entry中的强援用无奈回收,咱们晓得 ThreadlocalMap 是属于Thread的,如果是服务端线程的话,因为Thread长期存在,ThreadLocalMap 也必然长期存在,那么对应的这个Entry也会长期存在,那这个ThreadLocal 对象就不会被回收,就造成了内存透露。所以这就是为什么要应用弱援用的起因。
除此之外,JDK的设计者曾经帮咱们应用弱援用来防止了内存透露,认真想想这样就不会再产生内存透露了吗?当对应的Entry 中的key被回收,那这个value是不是就无奈获取到了呢,但因为Thread长期存在,ThreadLocalMap也长期存在,Entry也会长期存在,value也会永远都无奈开释了,这样还是会造成内存透露。所以,在每次应用完之后,都须要调用remove 办法进行资源革除。

说到这里,回忆一下如果在拦截器里应用ThreadLocal,就能了解为什么在 afterCompletion 须要remove办法了吧,如果不进行资源革除,就会导致线程在第二次申请中get到第一次申请的set进去的值。

remove()

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

remove 操作还是比较简单的,就是通过以后Thread 对象获取 ThreadLocalMap ,若不为空则再依据 ThreadLocal 对象作为key删除value。

get()

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();}
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);    }    if (this instanceof TerminatingThreadLocal) {        TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);    }    return value;}

接着咱们来看下get 操作,get 操作同样也是先获取以后ThreadThreadLocalMap,再依据ThreadLocal对象获取对应的Entry,最初获取值。若map为空,则初始化值而后将初始化的值返回。

利用实际

Spring事务管理

在Web我的项目编程中,咱们都会与数据库进行打交道,往往通常的做法是一个Service层里蕴含了多个Dao层的操作,要保障Service层操作的原子性,就要保障这些Dao操作是在同一个事务里,在同一个事务里就要确保多个Dao层的操作都是同一个Connection,那如何保障呢?咱们能够确定的是该多个Dao层的操作都是由雷同的线程进行解决的,那只有把Connection与线程绑定就能够了,所以Spring这里就奇妙的应用ThreadLocal来解决了这个问题。
Spring中有一个类 DataSourceUtils 其中有办法是获取数据源的Connection的,外面有一个getConnection办法,如下所示。

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {    try {        return doGetConnection(dataSource);    }    catch (SQLException ex) {        throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);    }    catch (IllegalStateException ex) {        throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());    }}
for example when using {@link DataSourceTransactionManager}. Will bind a Connection to the
thread if transaction synchronization is active
这里的正文有一个细节要关注到就是正文中提及到 如果应用数据源事务管理器,当开启事务时,那么就会绑定连贯到以后线程。
/*** Actually obtain a JDBC Connection from the given DataSource.* Same as {@link #getConnection}, but throwing the original SQLException.* <p>Is aware of a corresponding Connection bound to the current thread, for example* when using {@link DataSourceTransactionManager}. Will bind a Connection to the thread* if transaction synchronization is active (e.g. if in a JTA transaction).* <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.* @param dataSource the DataSource to obtain Connections from* @return a JDBC Connection from the given DataSource* @throws SQLException if thrown by JDBC methods* @see #doReleaseConnection*/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(fetchConnection(dataSource));        }        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 = fetchConnection(dataSource);    if (TransactionSynchronizationManager.isSynchronizationActive()) {        try {            // 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);            }        }        catch (RuntimeException ex) {            // Unexpected exception from external delegation call -> close Connection and rethrow.            releaseConnection(con, dataSource);            throw ex;        }    }    return con;}

从源代码大抵能够看出首先从TransactionSynchronizationManager中获取ConnectionHolder,若存在则间接返回Connection,若不存在则新生成一个Connection并装到ConnectionHolder中而后注册到TransactionSynchronizationManager 中,而后再返回Connection。由此,咱们能够看出TransactionSynchronizationManager 在这其中起到了治理Connection的作用。

接着看下TransactionSynchronizationManager 类。其中getResource办法和bindResource办法都在下面的doGetConnection办法中有过调用,那咱们就重视看下这几个办法。

public abstract class TransactionSynchronizationManager {private static final ThreadLocal<Map<Object, Object>> resources =            new NamedThreadLocal<>("Transactional resources");        private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =            new NamedThreadLocal<>("Transaction synchronizations");    private static final ThreadLocal<String> currentTransactionName =            new NamedThreadLocal<>("Current transaction name");    private static final ThreadLocal<Boolean> currentTransactionReadOnly =            new NamedThreadLocal<>("Current transaction read-only status");    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =            new NamedThreadLocal<>("Current transaction isolation level");    private static final ThreadLocal<Boolean> actualTransactionActive =            new NamedThreadLocal<>("Actual transaction active");...public static Object getResource(Object key) {    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);    Object value = doGetResource(actualKey);    if (value != null && logger.isTraceEnabled()) {        logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +                     Thread.currentThread().getName() + "]");    }    return value;}    private static Object doGetResource(Object actualKey) {    Map<Object, Object> map = resources.get();    if (map == null) {        return null;    }    Object value = map.get(actualKey);    // Transparently remove ResourceHolder that was marked as void...    if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {        map.remove(actualKey);        // Remove entire ThreadLocal if empty...        if (map.isEmpty()) {            resources.remove();        }        value = null;    }    return value;}...public static void bindResource(Object key, Object value) throws IllegalStateException {    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);    Assert.notNull(value, "Value must not be null");    Map<Object, Object> 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() + "]");    }}

从以上代码能够得出TransactionSynchronizationManager 类保护了ThreadLocal对象来进行资源的存储,包含事务资源(Spring中对JDBC的Connection或Hibernate的Session都称之为资源),事务隔离级别等。
名为resources变量的ThreadLocal对象存储的是DataSource生成的actualKey为key值和ConnectionHolder作为value值封装成的Map。
再联合DataSourceUtilsdoGetConnection办法和TransactionSynchronizationManagerbindResourcegetResource办法可知:在某个线程第一次调用时候,封装Map资源为:key值为DataSource生成actualKey和value值为DataSource取得的Connection对象封装后的ConnectionHolder。等这个线程下一次再次拜访中就能保障应用的是第一次创立的ConnectionHolder中的Connection对象。

参考链接

【死磕Java并发】—–深入分析ThreadLocal
Spring事务之如何保障同一个Connection对象
Spring是如何保障同一事务获取同一个Connection的?应用Spring的事务同步机制解决:数据库刚插入的记录却查问不到的问题【享学Spring】