ThreadLocal,直译就是本地线程,是多线程的知识点。线程安全的一个解决方案。
在多线程时,共享一个变量容易出现并发问题,特别是多个线为变量写入值的时候,如果要保证线程的安全性,就需要采取额外的线程安全措施(比如加锁,但是性能很低)。而ThreadLocal就可以更好的解决多线程之间变量的安全问题。
ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,每个线程中存储的变量对别的线程而言是相对隔离的,防止自己线程中的变量被其他线程修改。
ThreadLocal由JDK提供,他提供线程本地变量,如果创建一个ThreadLocal变量(ThreadLocal是一个Java类),那么访问这个变量的每一个线程都会有这个ThreadLocal类型变量的副本。之后,当一个线程操作这个变量的时候,操作的是自己本地内存中的ThreadLocal变量,并不会影响到其他线程中的ThradLocal对象,很好的规避了线程安全问题。
如下图:
ThreadLocal的方法很简单,就是三个方法就🆗了(可打开ThreadLocal类源码查看详细)
static final ThreadLocal<T> THREAD_LOCAL = new ThreadLocal<T>();
THREAD_LOCAL.set('参数'); // 只能存在一个参数
THREAD_LOCAL.get(); // 获取 无需传递任何参数
THREAD_LOCAL.remove(); // 删除key-value及时释放内存
那么原理呢?请看下面
可以从Thread、ThreadLocal二者的源码开始。
ThreadLocal类结构图:
Thread类中有threadLocals和inheritableThreadLocals两个变量,两者都是ThreadLocal内部类ThreadLocalMap类型的变量。可以通过查看ThreadLocalMap源码发现它的结构类似于HashMap(但是它并未实现Map接口)。
结合源码食用更爽~
初始情况下,threadLocals和inheritableThreadLocals都为null,只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们。
// Thread类内部
ThreadLocal.ThreadLocalMap threadLocals = null;
// some comments ...
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
并且,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程Thread的threadLocals变量里面。即 ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用当前线程的threadLocals中,当调用ThreadLocal的get方法,就能够从它的threadLocals中取出变量。
如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。
注意,数据存储在Thread类的threadLocals中(k-v)
其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。查看ThreadLocal的set方法:
// ThreadLocal的set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value); // 点击进入查看createMap()源码深入理解
}
}
每个线程的本地TheadLocalMap类型的变量存放在自己的本地内存变量threadLocals中,如果当前线程一直存在,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。
每个线程都持有一个ThradLocalMap类。ThreadLocalMap作为ThreadLocal的静态内部类,它为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,此下标就是value参数在数组中存储的位置。
ThreadLocalMap的底层是以数组形式存储参数value的(即存储在 Entry[]
数组中)。
虽然命名上,存在一个Map,但是通过源码发现,ThreadLocalMap并未实现Map的任何接口。它的Entry继承了WeakReference(弱引用)
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;
}
}
... ...
}
每一个线程具有自己的栈内存,可以理解为线程的私有内存,而堆内存中的对象所有线程可见、可访问。
但是,ThradLocal的实例和存储的参数并不是存储在栈内存上,相反,它们存储在堆内存中。原因如下:
ThreadLocal实例是被其创建的类所持有(更顶端应该是被线程所持有),它们都位于堆上,通过一些技巧将可见性修改成为仅指定线程可见。
先复习一下强引用和弱引用:
OutOfMemoryError
错误,也不回收。java.lang.ref.WeakReference
类来表示弱引用。每一个Thread都会维护一个ThreadLocalMap(Thread源码可知),key为使用弱引用的ThreadLocal实例,value为线程存储的变量值。
再来看一张很熟悉的图片:
其中实线代表强引用,虚线代表弱引用。也就是ThreadLocalMap里面的key为弱引用(如果不是弱引用,会内存泄漏),就会出现如下问题:
如果当前线程还存在,当前线程的ThreadLocalMap里面的key,弱引用于ThreadLocal变量,在gc的时候就key被回收,但是对应的value还是存在。这就可能造成内存泄漏(因为这个时候ThreadLocalMap会存在key为null,value不为null的entry项,value存储在当前线程的空间上,会一直存在系统的本地内存中)。
回到TheadLocal的set()方法:一个ThradLocal对象它只能存储唯一的映射,key是当前ThreadLocal对象,value是存储的对象(注意,他不会自己被回收)。当调用set()
方法时,会重新构建一个key-value的entry映射,之前存储的value并不会被gc回收,这就导致了之前存储的value还存在,新存储的value也存在。此时却只能获取到新构建的映射对象,之前存储的对象获取不到,当这种value过多的时候,就出现了内存泄漏的问题。
这就需要在使用完毕时,及时调用remove方法把key和value都删除,避免内存泄漏。
Spring事物隔离级别源码有使用到(Spring的事物主要是ThreadLocal和AOP去实现的。)。
Spring采用ThreadLocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接(即每个线程的连接数据库的connection对象存储在Thread中的ThreadLocalMap中),采用这种方式使得业务层在使用事物时不需要感知和管理connection对象(直接获取就能使用)。在通过事物的传播级别,巧妙的管理多个事物配置之间的切换、挂起和恢复。
在一个线程中横跨若干方法调用一个对象,也就是上下文(Context)(它是一种状态,经常就是是用户身份、任务信息等),就会存在如何传参的问题。
把参数传递一层方法一层方法的传递显然不合适,而使用ThreadLocal就可以很好的解决这个问题:
线程初始时可初始化一个泛型的ThreadLocal对象,在参数初始位置使用ThreadLocal调用 set('参数')
方法后,可在同一个线程的不同地方使用ThreadLocal类的get()
方法获取(remove之前),获取完数据后务必及时remove掉,避免内存泄漏。
例如编写一个工具(保存用户信息到当前线程空间):
public class UserThreadLocal {
private UserThreadLocal(){}
//线程变量隔离
private static final ThreadLocal<SysUser> THREAD_LOCAL = new ThreadLocal<>();
/** * 存储sysUser * @param sysUser */
public static void putUser(SysUser sysUser){
THREAD_LOCAL.set(sysUser);
}
/** * 获取sysUser * @return */
public static SysUser getUser(){
return THREAD_LOCAL.get();
}
/** * 删除sysUser */
public static void removeUser(){
THREAD_LOCAL.remove();
}
}
在适当的位置调用对应的方法即可实现同线程之间传递用户参数。
ThreadLocal和Synchronized都是可以解决多线程中相同变量的访问冲突问题,不同的点是:
相比于Synchronized,ThreadLocal具有线程隔离的效果,实现在单独的线程内才能获取到对于的值,其他线程则访问不到。很明显,在两者都能使用的情况下,优先选择ThreadLocal(Synchronized也有它必须做的事)。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/yeahPeng11/article/details/120839039
内容来源于网络,如有侵权,请联系作者删除!