ThreadLocal
作用:
用于数据隔离,说白了就是每个线程只能操作自己的变量,避免了冲突问题。
原理:
实现原理的核心就是ThreadLocalMap
1 每个thread里面维护着一个ThreadLocalMap变量
2 ThreadLocalMap 是一个定义在 ThreadLocal 类中的内部类,是一个 map,用Entry 来进行数据存储。
Thread,ThreadLocal, ThreadLocalMap之间的关系就是实现原理
? 键:这个ThreadLocal
每个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647
? 值:实际需要的成员变量
常用方法:
1 . public T get() 获取当前线程的副本变量值。
当调用 ThreadLocal 的 get() 方法时,也是先获取当前线程的 ThreadLocalMap 对象,以 ThreadLocal 对象作为 key 从
ThreadLocalMap 中获取值。
//
获取当前线程中的变量副本 // 获取当前线程 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
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); } |
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。
内存泄露:
由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。