Java的ThreadLocal源码详细解读
作者:疯狂的帆
ThreadLocal是什么
ThreadLocal翻译过来就是线程本地,也就是本地线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。
Threadlocal 主要用来做线程变量的隔离。
ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,各个线程间互不影响,从而实现线程安全。
ThreadLocal怎么用
public static void main(String[] args) { IntStream.range(1, 5).forEach(i -> new Thread(() -> { // 设置线程中本地变量的值 threadLocal.set("thread-" + i); // 打印当前线程中本地内存中本地变量的值 System.out.println(threadLocal.get()); // 清除本地内存中的本地变量 threadLocal.remove(); // 打印本地变量 System.out.println("thread-" + i + " after remove: " + threadLocal.get()); }).start()); } /* thread-1 thread-4 thread-4 after remove: null thread-2 thread-3 thread-2 after remove: null thread-1 after remove: null thread-3 after remove: null */
从结果可以看到,每一个线程都有各自的值,并且互不影响。
应用场景
- 每秒钟同时会有很多用户请求,那每个请求都带有用户信息,我们知道通常都是一个线程处理一个用户请求,我们可以把用户信息丢到Threadlocal里面,让每个线程处理自己的用户信息,线程之间互不干扰。
- 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
- 线程间数据隔离。
- 进行事务操作,用于存储线程事务信息。
- 数据库连接,Session会话管理。
ThreadLocal源码分析
先看一下 ThreadLocal 和 Thread 的关系
Thread类中有一个threadLocals属性,是ThreadLocal内部类ThreadLocalMap类型的变量,ThreadLocalMap可以看作是一个HashMap,其内部有一个内部类为 Entry,继承了 WeakReference<ThreadLocal<?>>
,是一个弱引用。Entry的key是 ThreadLocal<?>
,value是Object类型的值。
大致了解了Thread和ThreadLocal的关系之后,看一下Thread Local的源码: 我们只要看其主要的几个方法,就可以完全了解ThreadLocal的原理了。
set方法
public void set(T value) { // 获取当前线程 Thread t = Thread.currentThread(); // 通过当前线程获取线程中的ThreadLocal.ThreadLocalMap对象 ThreadLocalMap map = getMap(t); if (map != null) // map不为空,则直接赋值 map.set(this, value); else // map为空,则创建一个ThreadLocalMap对象 createMap(t, value); } // 根据提供的线程对象,和指定的值,创建一个ThreadLocalMap对象 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } // threadLocals是Thread类的一个属性 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /* Thread 类 182行 // ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. ThreadLocal.ThreadLocalMap threadLocals = null; */
get方法
// ThreadLocalMap中的内部类,存放key,value static class Entry extends WeakReference<ThreadLocal<?>> { // 与此ThreadLocal关联的值 Object value; // k:ThreadLocal的引用,被传递给WeakReference的构造方法 Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } public T get() { // 获取当前线程 Thread t = Thread.currentThread(); // 通过当前线程获取线程中的ThreadLocal.ThreadLocalMap对象 ThreadLocalMap map = getMap(t); if (map != null) { // map不为空,通过this(当前对象,即ThreadLocal对象)获取Entry对象 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; // Entry不为空,则直接返回Entry中的value值 return result; } } // 如果map或Entry为空,则返回初始值-null return setInitialValue(); } // 设置初始值,初始化ThreadLocalMap对象,并设置value为 null private T setInitialValue() { // 初始化值,此方法返回 null T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
remove方法
public void remove() { // 通过当前线程获取线程中的ThreadLocal.ThreadLocalMap对象 ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) // 移除对象 m.remove(this); } // 根据key,删除对应的所有值 private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; // 获取key对应的 Entry[] 下标 int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; // 获取下一个Entry对象 e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); // 通过重新哈希位于staleSlot和下一个null插槽之间的任何可能冲突的条目,来清除陈旧的条目。这还会清除尾随null之前遇到的所有其他过时的条目,防止出现内存泄漏问题 expungeStaleEntry(i); return; } } }
总结
- 每个Thread维护着一个ThreadLocalMap的引用
- ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
- ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。
- ThreadLocalMap的键为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中
- 在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。
- ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。
InheritableThreadLocal
ThreadLocal类:同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。
而InheritableThreadLocal类,通过继承ThreadLocal类,并重写了childValue、getMap、createMap三个方法,是可以在子线程中获取到父线程的值。
InheritableThreadLocals类通过重写getMap和createMap两个方法将本地变量保存到了具体线程的inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数会将父线程的inheritableThreadLocals中的变量复制一份到子线程的inheritableThreadLocals变量中。
ThreadLocal内存泄漏问题
简单说一下Java中的4种引用
- 强引用:不管JVM如何gc,都不会被回收。
- 软引用:只有JVM内存不足,进行fullGc时,才会被回收。
- 弱引用:只要出现gc,就会被回收。
- 虚引用:虚引用是所有引用中最弱的一种引用,其存在就是为了将关联虚引用的对象在被GC掉之后收到一个通知。(不能通过get方法获得其指向的对象)
在上面的代码中,我们可以看出,当前ThreadLocal的引用k被传递给WeakReference的构造函数,所以ThreadLocalMap中的key为ThreadLocal的弱引用。
如果当前线程一直存在且没有调用该ThreadLocal的remove方法,如果这个时候别的地方还有对ThreadLocal的引用,那么当前线程中的ThreadLocalMap中会存在对ThreadLocal变量的引用和value对象的引用,是不会释放的,会造成内存泄漏。
ThreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的value值不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,也会造成内存泄漏。
使用完ThreadLocal后,一定执行remove操作,避免出现内存泄漏情况。
到此这篇关于Java的ThreadLocal源码详细解读的文章就介绍到这了,更多相关Java的ThreadLocal内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!