ThreadLocal学习

300 阅读3分钟

ThreadLocal

ThreadLocal是线程的内部存储类,可以在指定的线程内存储数据,只有指定线程可以得到存储数据。

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有的ThreadLocal对象以及其所有的值。

image.png

image.png

image.png

/**
 * 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存储的对应位置。 image.png

对于一个ThreadLocal来讲,其索引值i是确定的,对于不同线程,同一个threadLocal对应的不是table的同一下标,即是table[i],不同线程之间的table是相互独立的。

线程隔离特性

  • Synchronized是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突

内存泄露问题

存在内存泄漏问题,每次使用完ThreadLocal,都必须调用remove()方法进行清除。

image.png

Entry继承自WeakReference<ThreadLocal<?>>,一个Enrty由ThreadLocal对象和objects 构成。Entry的key是ThreadLocal对象,并且是一个弱引用。当没有指向key的强引用之后,该key就会被垃圾回收期回收。

如何避免ThreadLocal内存泄漏

不会再被使用的对象或者变量占用的内存不能被回收,就是内存泄漏。

强引用:使用最普遍的引用(new),一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,java虚拟机宁愿抛出OOM异常也不会回收这种对象。

弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。

image.png

线程的栈会保存一个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的实际使用

image.png 接口套接口

@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)));