ThreadLocal


作用:

用于数据隔离,说白了就是每个线程只能操作自己的变量,避免了冲突问题。

 

原理:

实现原理的核心就是ThreadLocalMap

 

1 每个thread里面维护着一个ThreadLocalMap变量

/* ThreadlocaZ values pertaining to this thread. This nFp is mintained  * by the Threadlocal class. */  ThreadLocal. ThreadLocalMap threadLocals = null;

 

2 ThreadLocalMap 是一个定义在 ThreadLocal 类中的内部类,是一个 map,用Entry 来进行数据存储。

static class ThreadLocatMap {  The entries in this hash map extend WeakReference, using its main ref field as the  always a ThreadLocal object). Note that null keys (I.e. entry.get() null) mean th  longer referenced, so the entry can be expunged from table. Such entries are reft  ?stale entries ? in the code that follows.  static class Entry extends WeakReference<ThreadLocal<?>> {  The value associated with this ThreadLocal.  Object value;  Entry(ThreadLocal<?> k,  super (k);  value = v;  Object v) {

 

Thread,ThreadLocal, ThreadLocalMap之间的关系就是实现原理

ThreadLocaIMap 1  ThreadLocal 1  ThreadLocal 2  ThreadLocal n  ThreadLocaIMap 2  ThreadLocal 1  ㄒ h 「 eadLoc 刨 2  readLocal m

? 键:这个ThreadLocal

每个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647

? 值:实际需要的成员变量

 

 

常用方法:

1 . public T get() 获取当前线程的副本变量值。

当调用 ThreadLocal 的 get() 方法时,也是先获取当前线程的 ThreadLocalMap 对象,以 ThreadLocal 对象作为 key 从

 

ThreadLocalMap 中获取值。

// 获取当前线程中的变量副本
public T get() {

    // 获取当前线程

    Thread t = Thread.currentThread();

    // 获取线程中的ThreadLocalMap对象

    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;

        }

    }

    // 若没有该变量副本,返回setInitialValue()

    return setInitialValue();

}

 

2 . public void set(T value) 保存当前线程的副本变量值。

当调用 ThreadLocal 的 set() 方法时,先获取当前线程的 ThreadLocalMap 对象,然后以 ThreadLocal 对象作为 key 往ThreadLocalMap 中设置值。

注意:每个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647

private final int threadLoca1HashCode = nextHashCode();  The next hash code to be given out. Updated atomically. Starts at zero.  private static Ataniclnteger nextHashCode =  new Ataniclnteger();  The difference between successively generated hash codes - turns implicit sequential thread-local  IDs into near-optimally spread multiplicative hash values for power-of-two-sized tables.  private static final int HASH_INCREMENT = 8x61c88647;  Returns the next hash code.  private static int nextHashCode  { return nextHashCode.getAndAdd(HASH_INCREMENT);

 

public void set(T value) {

    Thread t = Thread.currentThread();

    ThreadLocalMap map = getMap(t);

    if (map != null)

        // map不为空,直接将ThreadLocal对象作为key

        // 变量本身的值为value,存入map

        map.set(this, value);

    else

        // 否则,创建ThreadLocalMap

        createMap(t, value);

}

 

ThreadLocatMap getMap(Thread t) {  return t. threadLocals;

 

GET值的过程:

在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置,set和get如果冲突严重的话,效率还是很低的。

private Entry getEntry(ThreadLocal<?> key) {

            int i = key.threadLocalHashCode & (table.length - 1);

            Entry e = table[i];

            if (e != null && e.get() == key)

                return e;

            else

                return getEntryAfterMiss(key, i, e);

        }

 

 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {

            Entry[] tab = table;

            int len = tab.length;

// get的时候一样是根据ThreadLocal获取到table的i值,然后查找数据拿到后会对比key是否相等  if (e != null && e.get() == key)。

            while (e != null) {

                ThreadLocal<?> k = e.get();

              // 相等就直接返回,不相等就继续查找,找到相等位置。

                if (k == key)

                    return e;

                if (k == null)

                    expungeStaleEntry(i);

                else

                    i = nextIndex(i, len);

                e = tab[i];

            }

            return null;

        }

 

 

应用场景:

1 Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接.

2 我们每次对数据库操作,都会走JDBC getConnection,JDBC保证只要你是同一个线程过来的请求,不管是在哪个part,都返回的是同一个连接。这个就是使用ThreadLocal来做的。

3 ThreadLocal给每个线程分配一个SimpleDateFormat对象。

4 全局变量的传递,通常可以通过AOP或者拦截器的方式进行赋值,执行完业务逻辑之后调用remove()方法。

5 Mybatis使用SqlSessionManager保证了我们同一个线程取出来的连接总是同一个。就是内部使用了一个ThreadLocal。

 

 

内存泄露:

m??J靄|

 

由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。