ThreadLocal
ThreadLocal是线程的内部存储类,可以在指定的线程内存储数据,只有指定线程可以得到存储数据。
每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有的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).
*/
/**
*此类提供线程局部变量。这些变量不同于
*它们的正常对应关系是,每个访问一个线程的线程(通过
*{@代码获取}或{@代码集}方法有它自己的、独立初始化的
*变量的副本。{@code ThreadLocal}实例通常是私有的
*希望将状态与线程关联的类中的静态字段(例如。,
*用户ID或事务ID)。
*/
每个线程都有一个TheadLocalMap的实例对象,并且通过ThreadLocal管理 ThreadLocalMap,每个新线程都会实例化为一个ThreadLocalMap并且赋值给成员变量ThreadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。(懒加载)
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
set()、get()会对null值得进行清理,通过调用expungeStaleEntry()
remove()会在垃圾回收之前将弱引用断开,然后将key为null的value进行清理
//可见堆中这个ThreadLocalMap的名字叫做threadLocals
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//private final static ThreadLocal<SecurityContext> CONTEXT_HOLDER = new ThreadLocal<>();
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//this: CONTEXT_HOLDER
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
应用场景
当某些数据是以线程为作用域并且不同线程有不同数据副本时,考虑ThreadLocal。
无状态,副本变量独立后不影响业务逻辑的高并发场景。如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal进行解决。
SET和GET调用ThreadLocalMap的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);
}
//getMap方法
ThreadLocalMap getMap(Thread t) {
//thred中维护了一个ThreadLocalMap
return t.threadLocals;
}
//createMap
void createMap(Thread t, T firstValue) {
//实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap
ThreadLocalMap为每一个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标是value存储的对应位置。
对于一个ThreadLocal来讲,其索引值i是确定的,对于不同线程,同一个threadLocal对应的不是table的同一下标,即是table[i],不同线程之间的table是相互独立的。
线程隔离特性
- Synchronized是通过线程等待,牺牲时间来解决访问冲突
- ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突
内存泄露问题
存在内存泄漏问题,每次使用完ThreadLocal,都必须调用remove()方法进行清除。
Entry继承自WeakReference<ThreadLocal<?>>,一个Enrty由ThreadLocal对象和objects 构成。Entry的key是ThreadLocal对象,并且是一个弱引用。当没有指向key的强引用之后,该key就会被垃圾回收期回收。
如何避免ThreadLocal内存泄漏
不会再被使用的对象或者变量占用的内存不能被回收,就是内存泄漏。
强引用:使用最普遍的引用(new),一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,java虚拟机宁愿抛出OOM异常也不会回收这种对象。
弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。
线程的栈会保存一个ThreadLocal的引用和一个CurrentThreadRef的引用。ThreadLocal是一个强引用,当强引用消失之后,ThreadLocalMap中的(key ---》ThreadLocal)这条弱引用就会消失,此时虽然key消失了,但是其value仍然存在着引用,直到线程退出后,value的值才会被清除。
弱引用的也解决不了,直到线程退出才能消除引用。
在下一次ThreadLocalMap中的set()、get()、remove()任何一个方法都会使得value被清除
正确的使用方法
- 每次使用完ThreadLocal都调用其remove()方法清除数据
- 将ThreadLocal变量定义成static,这样就一直存在着ThreadLocal的强引用,而不是一个普通 new的强引用有可能变成null的状态,也就能保证任何时刻都可通过ThreadLocal的弱引用访问到Entry的value的值,进而进行清除。
Halo的实际使用
接口套接口
@NonNull
public static SecurityContext getContext() {
// Get from thread local
SecurityContext context = CONTEXT_HOLDER.get();
if (context == null) {
// If no context is available now then create an empty context
context = createEmptyContext();
// Set to thread local
CONTEXT_HOLDER.set(context);
}
return context;
}
public static void setContext(@Nullable SecurityContext context) {
CONTEXT_HOLDER.set(context);
}
// Check user login status and set this field
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
// Blogger comment
User user = authentication.getDetail().getUser();
commentParam.setAuthor(StringUtils.isBlank(user.getNickname()) ? user.getUsername() : user.getNickname());
commentParam.setEmail(user.getEmail());
commentParam.setAuthorUrl(optionService.getByPropertyOrDefault(BlogProperties.BLOG_URL, String.class, null));
}
// Validate the comment param manually
ValidationUtils.validate(commentParam);
// Get the user
User user = userService.getById(optionalUserId.get());
// Build user detail
UserDetail userDetail = new UserDetail(user);
// Set security
SecurityContextHolder.setContext(new SecurityContextImpl(new AuthenticationImpl(userDetail)));